diff --git a/.gitignore b/.gitignore index 6314fc6d..b90386b3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ node_modules .env .dev.vars .sentryclirc + +playwright-report +test-results + diff --git a/e2e-tests/joining-room.spec.ts b/e2e-tests/joining-room.spec.ts new file mode 100644 index 00000000..85aa602b --- /dev/null +++ b/e2e-tests/joining-room.spec.ts @@ -0,0 +1,28 @@ +import { expect, test } from '@playwright/test' + +test('Two users joining the same room', async ({ browser }) => { + // can't use nanoid here :( + const location = `http://localhost:8787/${crypto.randomUUID()}` + + const context = await browser.newContext() + const page = await context.newPage() + await page.goto(location) + await page.getByLabel('Enter your display name').fill('kevin') + await page.getByLabel('Enter your display name').press('Enter') + await expect(page.getByRole('button', { name: 'Join' })).toBeVisible() + await page.getByRole('button', { name: 'Join' }).click() + await expect(page.getByRole('button', { name: 'Leave' })).toBeVisible() + + const pageTwo = await context.newPage() + await pageTwo.goto(location) + await pageTwo.getByRole('button', { name: 'Join' }).click() + await expect(pageTwo.getByRole('button', { name: 'Leave' })).toBeVisible() + + await expect + .poll(async () => page.locator('video').count(), { timeout: 10_000 }) + .toBe(2) + + await expect + .poll(async () => pageTwo.locator('video').count(), { timeout: 10_000 }) + .toBe(2) +}) diff --git a/package-lock.json b/package-lock.json index 1f4300bd..38fa48d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "@cloudflare/vitest-pool-workers": "^0.4.19", "@cloudflare/workers-types": "^4.20240806.0", "@peculiar/webcrypto": "^1.5.0", + "@playwright/test": "^1.46.1", "@remix-run/dev": "2.11.1", "@remix-run/eslint-config": "2.11.1", "@types/react": "^18.3.3", @@ -2297,6 +2298,21 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz", + "integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==", + "dev": true, + "dependencies": { + "playwright": "1.46.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -11652,6 +11668,50 @@ "pathe": "^1.1.0" } }, + "node_modules/playwright": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz", + "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==", + "dev": true, + "dependencies": { + "playwright-core": "1.46.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz", + "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", @@ -17130,6 +17190,15 @@ "dev": true, "optional": true }, + "@playwright/test": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz", + "integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==", + "dev": true, + "requires": { + "playwright": "1.46.1" + } + }, "@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -23702,6 +23771,31 @@ "pathe": "^1.1.0" } }, + "playwright": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz", + "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.46.1" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz", + "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==", + "dev": true + }, "postcss": { "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", diff --git a/package.json b/package.json index a8d972e5..305db5f1 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "typecheck:watch": "npm run typecheck -- --watch", "test": "vitest", "test:ci": "vitest --watch false", + "test:e2e": "playwright test --headed", "check": "npm run lint && npm run typecheck && npm run test:ci" }, "dependencies": { @@ -59,6 +60,7 @@ "@cloudflare/vitest-pool-workers": "^0.4.19", "@cloudflare/workers-types": "^4.20240806.0", "@peculiar/webcrypto": "^1.5.0", + "@playwright/test": "^1.46.1", "@remix-run/dev": "2.11.1", "@remix-run/eslint-config": "2.11.1", "@types/react": "^18.3.3", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..07c97504 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,92 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * 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: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 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: 'http://localhost:8787', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + video: 'on-first-retry', + ...devices['Desktop Chrome'], + launchOptions: { + args: [ + '--disable-web-security', + '--use-fake-ui-for-media-stream', + '--use-fake-device-for-media-stream', + ], + }, + }, + }, + + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + url: 'http://localhost:8787', + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/vitest.config.mts b/vitest.config.mts index 7cabdc63..7cf2f957 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -2,6 +2,9 @@ import path from 'path' import { defineConfig } from 'vitest/config' export default defineConfig({ + test: { + exclude: ['**/e2e-tests/**', '**/node_modules/**'], + }, resolve: { alias: { '~': path.resolve(__dirname, './app'),