Skip to content

Commit

Permalink
feat(client): added e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
CorentinTh committed Sep 17, 2024
1 parent 53a3e4d commit 99ccf4e
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 39 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/ci-deploy-cloudflare.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ jobs:

- name: Build the app
run: pnpm build

- name: Verify build
run: |
if [ ! -f dist/_worker.js ]; then
echo "dist/_worker.js not found"
exit 1
fi
if [ ! -f dist/index.html ]; then
echo "dist/index.html not found"
exit 1
fi
53 changes: 53 additions & 0 deletions .github/workflows/ci-test-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: E2E Tests

on:
deployment_status:

jobs:
run-e2es:
if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: packages/app-client

steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
corepack: true
cache: 'pnpm'

- name: Get Playwright version
id: playwright-version
run: echo "PLAYWRIGHT_VERSION=$(jq -r .dependencies.playwright package.json)" >> "$GITHUB_OUTPUT"

- name: Install dependencies
run: pnpm i
working-directory: ./

- name: Restore Playwright browsers from cache
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}-${{ hashFiles('**/playwright.config.ts') }}
restore-keys: |
${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}-
${{ runner.os }}-playwright-
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps

- name: Run e2e tests
run: pnpm test:e2e
env:
BASE_URL: ${{ github.event.deployment_status.environment_url }}

- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 15
7 changes: 6 additions & 1 deletion packages/app-client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ gitignore
.DS_Store
Thumbs.db

cache
cache

/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
31 changes: 31 additions & 0 deletions packages/app-client/e2e-tests/createand-view-note.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect, test } from '@playwright/test';

test('Can create and view a note', async ({ page }) => {
await page.goto('/');

await expect(page).toHaveTitle('Enclosed - Send private and secure notes');

// Write a note with a password and delete after reading
await page.getByTestId('note-content').fill('Hello, World!');
await page.getByTestId('note-password').fill('my-cat-is-cute');
await page.getByTestId('delete-after-reading').click();

await page.getByTestId('create-note').click();
const noteUrl = await page.getByTestId('note-url').inputValue();

expect(noteUrl).toBeDefined();

await page.goto(noteUrl);

await page.getByTestId('note-password-prompt').fill('my-cat-is-cute');
await page.getByTestId('note-password-submit').click();

const noteContent = await page.getByTestId('note-content-display').textContent();

expect(noteContent).toBe('Hello, World!');

// Refresh the page and check if the note is still there
await page.reload();

await expect(page.getByText('Note not found')).toBeVisible();
});
6 changes: 3 additions & 3 deletions packages/app-client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<link rel="preload" href="/api/config" as="fetch" crossorigin="anonymous" />

<meta name="title" content="Enclosed - Send Private and Secure Notes">
<meta name="title" content="Enclosed - Send private and secure notes">
<meta name="description" content="Enclosed is a secure, end-to-end encrypted platform for sending private notes. Set expiration, passwords, and self-destruct options to keep your notes confidential.">

<link rel="author" href="humans.txt" />
Expand All @@ -17,14 +17,14 @@
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://enclosed.cc/">
<meta property="og:title" content="Enclosed - Send Private and Secure Notes">
<meta property="og:title" content="Enclosed - Send private and secure notes">
<meta property="og:description" content="Enclosed is a secure, end-to-end encrypted platform for sending private notes. Set expiration, passwords, and self-destruct options to keep your notes confidential.">
<meta property="og:image" content="https://enclosed.cc/og-image.png">

<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://enclosed.cc/">
<meta property="twitter:title" content="Enclosed - Send Private and Secure Notes">
<meta property="twitter:title" content="Enclosed - Send private and secure notes">
<meta property="twitter:description" content="Enclosed is a secure, end-to-end encrypted platform for sending private notes. Set expiration, passwords, and self-destruct options to keep your notes confidential.">
<meta property="twitter:image" content="https://enclosed.cc/og-image.png">

Expand Down
3 changes: 3 additions & 0 deletions packages/app-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"test": "pnpm run test:unit",
"test:unit": "vitest run",
"test:unit:watch": "vitest watch",
"test:e2e": "playwright test",
"typecheck": "tsc --noEmit"
},
"dependencies": {
Expand All @@ -44,7 +45,9 @@
"devDependencies": {
"@antfu/eslint-config": "^3.0.0",
"@iconify-json/tabler": "^1.1.120",
"@playwright/test": "^1.46.1",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.5.0",
"eslint": "^9.10.0",
"jsdom": "^25.0.0",
"typescript": "^5.3.3",
Expand Down
52 changes: 52 additions & 0 deletions packages/app-client/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import process from 'node:process';
import { defineConfig, devices } from '@playwright/test';

const baseURL = process.env.BASE_URL ?? 'http://localhost:3000/';
const isCI = Boolean(process.env.CI);

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e-tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: isCI,
/* Retry on CI only */
retries: isCI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: isCI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL,

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',

testIdAttribute: 'data-test-id',
locale: 'en-GB',
timezoneId: 'Europe/Paris',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TextField } from '@/modules/ui/components/textfield';
import { type Component, createSignal } from 'solid-js';
import { createRandomPassword } from '../notes.models';

export const NotePasswordField: Component<{ getPassword: () => string; setPassword: (value: string) => void }> = (props) => {
export const NotePasswordField: Component<{ getPassword: () => string; setPassword: (value: string) => void; dataTestId?: string } > = (props) => {
const [getShowPassword, setShowPassword] = createSignal(false);
const { t } = useI18n();

Expand All @@ -17,7 +17,7 @@ export const NotePasswordField: Component<{ getPassword: () => string; setPasswo

return (
<div class="border border-input rounded-md flex items-center pr-1">
<TextField placeholder={t('create.settings.password.placeholder')} value={props.getPassword()} onInput={e => props.setPassword(e.currentTarget.value)} class="border-none shadow-none focus-visible:ring-none" type={getShowPassword() ? 'text' : 'password'} />
<TextField placeholder={t('create.settings.password.placeholder')} value={props.getPassword()} onInput={e => props.setPassword(e.currentTarget.value)} class="border-none shadow-none focus-visible:ring-none" type={getShowPassword() ? 'text' : 'password'} data-test-id={props.dataTestId} />

<Button variant="link" onClick={() => setShowPassword(!getShowPassword())} class="text-base size-9 p-0 text-muted-foreground hover:text-primary transition" aria-label={getShowPassword() ? 'Hide password' : 'Show password'}>
<div classList={{ 'i-tabler-eye': !getShowPassword(), 'i-tabler-eye-off': getShowPassword() }}></div>
Expand Down
17 changes: 11 additions & 6 deletions packages/app-client/src/modules/notes/pages/create-note.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,21 @@ export const CreateNotePage: Component = () => {
<Switch>
<Match when={!getIsNoteCreated()}>
<TextFieldRoot class="w-full ">
<TextArea placeholder={t('create.settings.placeholder')} class="flex-1 p-4 min-h-300px sm:min-h-700px" value={getContent()} onInput={e => updateContent(e.currentTarget.value)} />
<TextArea
placeholder={t('create.settings.placeholder')}
class="flex-1 p-4 min-h-300px sm:min-h-700px"
value={getContent()}
onInput={e => updateContent(e.currentTarget.value)}
data-test-id="note-content"
/>
</TextFieldRoot>

<div class="w-full sm:w-320px flex flex-col gap-4 flex-shrink-0">
<TextFieldRoot class="w-full">
<TextFieldLabel>
{t('create.settings.password.label')}
</TextFieldLabel>
<NotePasswordField getPassword={getPassword} setPassword={setPassword} />

<NotePasswordField getPassword={getPassword} setPassword={setPassword} dataTestId="note-password" />
</TextFieldRoot>

<TextFieldRoot class="w-full">
Expand Down Expand Up @@ -184,7 +189,7 @@ export const CreateNotePage: Component = () => {
{t('create.settings.delete-after-reading.label')}
</TextFieldLabel>
<SwitchUiComponent class="flex items-center space-x-2" checked={getDeleteAfterReading()} onChange={setDeleteAfterReading}>
<SwitchControl>
<SwitchControl data-test-id="delete-after-reading">
<SwitchThumb />
</SwitchControl>
<SwitchLabel class="text-sm text-muted-foreground">
Expand All @@ -194,7 +199,7 @@ export const CreateNotePage: Component = () => {
</TextFieldRoot>

<div>
<FileUploaderButton variant="secondary" class="mt-2 w-full" multiple onFilesUpload={({ files }) => setUploadedFiles(prevFiles => [...prevFiles, ...files])}>
<FileUploaderButton variant="secondary" class="mt-2 w-full" multiple onFilesUpload={({ files }) => setUploadedFiles(prevFiles => [...prevFiles, ...files])} data-test-id="create-note">
<div class="i-tabler-upload mr-2 text-lg text-muted-foreground"></div>
{t('create.settings.attach-files')}
</FileUploaderButton>
Expand Down Expand Up @@ -252,7 +257,7 @@ export const CreateNotePage: Component = () => {
</div>

<TextFieldRoot class="w-full max-w-800px mt-4">
<TextField value={getNoteUrl()} readonly class="w-full text-center" />
<TextField value={getNoteUrl()} readonly class="w-full text-center" data-test-id="note-url" />
</TextFieldRoot>

<div class="flex items-center gap-2 w-full mx-auto mt-2 justify-center flex-col sm:flex-row">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const RequestPasswordForm: Component<{ onPasswordEntered: (args: { password: str
<div>
<TextFieldRoot>
<TextFieldLabel>{t('view.request-password.form.label')}</TextFieldLabel>
<TextField type="password" placeholder={t('view.request-password.form.placeholder')} value={getPassword()} onInput={e => updatePassword(e.currentTarget.value)} autofocus />
<TextField type="password" placeholder={t('view.request-password.form.placeholder')} value={getPassword()} onInput={e => updatePassword(e.currentTarget.value)} autofocus data-test-id="note-password-prompt" />
</TextFieldRoot>
</div>
<Button class="w-full mt-4" type="submit">
<Button class="w-full mt-4" type="submit" data-test-id="note-password-submit">
<div class="i-tabler-lock-open mr-2 text-lg"></div>
{t('view.request-password.form.unlock-button')}
</Button>
Expand Down Expand Up @@ -262,7 +262,7 @@ export const ViewNotePage: Component = () => {
{getDecryptedNote() && (
<div class="flex-1 mb-4">
<div class="flex items-center gap-2 mb-4 justify-between">
<div class="text-muted-foreground">
<div class="text-muted-foreground" data-test-id="note-content-display">
{t('view.note-content')}
</div>
<CopyButton text={getDecryptedNote()!} variant="secondary" />
Expand Down
4 changes: 4 additions & 0 deletions packages/app-client/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import process from 'node:process';
import unoCssPlugin from 'unocss/vite';
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
import { configDefaults } from 'vitest/config';

export default defineConfig({
plugins: [
Expand All @@ -29,4 +30,7 @@ export default defineConfig({
'@': path.resolve(__dirname, './src'),
},
},
test: {
exclude: [...configDefaults.exclude, '**/*.e2e.test.ts'],
},
});
Loading

0 comments on commit 99ccf4e

Please sign in to comment.