From 876136fcca6acc4130ff6a4a18ceeb177a4d8ff8 Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Mon, 23 Sep 2024 21:31:40 -0600 Subject: [PATCH] add test for exercise 4.1 --- epicshop/.diffignore | 1 + epicshop/fix.js | 21 ++ epicshop/package-lock.json | 140 ++++++++- epicshop/package.json | 7 +- epicshop/test.js | 272 ++++++++++++++++++ .../01.problem.lazy/package.json | 4 +- .../01.problem.lazy/tests/code-split.test.js | 34 +++ .../tests/playwright.config.js | 41 +++ .../01.solution.lazy/package.json | 4 +- .../01.solution.lazy/tests/code-split.test.js | 34 +++ .../tests/playwright.config.js | 41 +++ .../02.problem.eager/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../02.problem.eager/tests/todo.test.js | 7 + .../02.solution.eager/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../02.solution.eager/tests/todo.test.js | 7 + .../03.problem.transition/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../03.problem.transition/tests/todo.test.js | 7 + .../03.solution.transition/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../03.solution.transition/tests/todo.test.js | 7 + .../01.problem.use-memo/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../01.problem.use-memo/tests/todo.test.js | 7 + .../01.solution.use-memo/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../01.solution.use-memo/tests/todo.test.js | 7 + .../02.problem.worker/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../02.problem.worker/tests/todo.test.js | 7 + .../02.solution.worker/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../02.solution.worker/tests/todo.test.js | 7 + .../03.problem.async/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../03.problem.async/tests/todo.test.js | 7 + .../03.solution.async/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../03.solution.async/tests/todo.test.js | 7 + .../06.rerenders/01.problem.memo/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../01.problem.memo/tests/todo.test.js | 7 + .../01.solution.memo/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../01.solution.memo/tests/todo.test.js | 7 + .../02.problem.comparator/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../02.problem.comparator/tests/todo.test.js | 7 + .../02.solution.comparator/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../02.solution.comparator/tests/todo.test.js | 7 + .../03.problem.primitives/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../03.problem.primitives/tests/todo.test.js | 7 + .../03.solution.primitives/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../03.solution.primitives/tests/todo.test.js | 7 + .../01.problem.virtualizer/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../01.problem.virtualizer/tests/todo.test.js | 7 + .../01.solution.virtualizer/package.json | 4 +- .../tests/playwright.config.js | 41 +++ .../tests/todo.test.js | 7 + package-lock.json | 20 ++ tsconfig.json | 130 ++++----- 67 files changed, 1590 insertions(+), 95 deletions(-) create mode 100644 epicshop/test.js create mode 100644 exercises/04.code-splitting/01.problem.lazy/tests/code-split.test.js create mode 100644 exercises/04.code-splitting/01.problem.lazy/tests/playwright.config.js create mode 100644 exercises/04.code-splitting/01.solution.lazy/tests/code-split.test.js create mode 100644 exercises/04.code-splitting/01.solution.lazy/tests/playwright.config.js create mode 100644 exercises/04.code-splitting/02.problem.eager/tests/playwright.config.js create mode 100644 exercises/04.code-splitting/02.problem.eager/tests/todo.test.js create mode 100644 exercises/04.code-splitting/02.solution.eager/tests/playwright.config.js create mode 100644 exercises/04.code-splitting/02.solution.eager/tests/todo.test.js create mode 100644 exercises/04.code-splitting/03.problem.transition/tests/playwright.config.js create mode 100644 exercises/04.code-splitting/03.problem.transition/tests/todo.test.js create mode 100644 exercises/04.code-splitting/03.solution.transition/tests/playwright.config.js create mode 100644 exercises/04.code-splitting/03.solution.transition/tests/todo.test.js create mode 100644 exercises/05.calculations/01.problem.use-memo/tests/playwright.config.js create mode 100644 exercises/05.calculations/01.problem.use-memo/tests/todo.test.js create mode 100644 exercises/05.calculations/01.solution.use-memo/tests/playwright.config.js create mode 100644 exercises/05.calculations/01.solution.use-memo/tests/todo.test.js create mode 100644 exercises/05.calculations/02.problem.worker/tests/playwright.config.js create mode 100644 exercises/05.calculations/02.problem.worker/tests/todo.test.js create mode 100644 exercises/05.calculations/02.solution.worker/tests/playwright.config.js create mode 100644 exercises/05.calculations/02.solution.worker/tests/todo.test.js create mode 100644 exercises/05.calculations/03.problem.async/tests/playwright.config.js create mode 100644 exercises/05.calculations/03.problem.async/tests/todo.test.js create mode 100644 exercises/05.calculations/03.solution.async/tests/playwright.config.js create mode 100644 exercises/05.calculations/03.solution.async/tests/todo.test.js create mode 100644 exercises/06.rerenders/01.problem.memo/tests/playwright.config.js create mode 100644 exercises/06.rerenders/01.problem.memo/tests/todo.test.js create mode 100644 exercises/06.rerenders/01.solution.memo/tests/playwright.config.js create mode 100644 exercises/06.rerenders/01.solution.memo/tests/todo.test.js create mode 100644 exercises/06.rerenders/02.problem.comparator/tests/playwright.config.js create mode 100644 exercises/06.rerenders/02.problem.comparator/tests/todo.test.js create mode 100644 exercises/06.rerenders/02.solution.comparator/tests/playwright.config.js create mode 100644 exercises/06.rerenders/02.solution.comparator/tests/todo.test.js create mode 100644 exercises/06.rerenders/03.problem.primitives/tests/playwright.config.js create mode 100644 exercises/06.rerenders/03.problem.primitives/tests/todo.test.js create mode 100644 exercises/06.rerenders/03.solution.primitives/tests/playwright.config.js create mode 100644 exercises/06.rerenders/03.solution.primitives/tests/todo.test.js create mode 100644 exercises/07.windowing/01.problem.virtualizer/tests/playwright.config.js create mode 100644 exercises/07.windowing/01.problem.virtualizer/tests/todo.test.js create mode 100644 exercises/07.windowing/01.solution.virtualizer/tests/playwright.config.js create mode 100644 exercises/07.windowing/01.solution.virtualizer/tests/todo.test.js diff --git a/epicshop/.diffignore b/epicshop/.diffignore index a0d293c76..60b4830c2 100644 --- a/epicshop/.diffignore +++ b/epicshop/.diffignore @@ -1,3 +1,4 @@ tsconfig.json countries-110m.json us-cities.json +tests/ diff --git a/epicshop/fix.js b/epicshop/fix.js index 511f09467..72d1d6b47 100644 --- a/epicshop/fix.js +++ b/epicshop/fix.js @@ -46,6 +46,7 @@ function relativeToWorkshopRoot(dir) { } await updatePkgNames() +await copyTestFiles() await updateTsconfig() async function updatePkgNames() { @@ -63,6 +64,26 @@ async function updatePkgNames() { } } +async function copyTestFiles() { + for (const app of exerciseApps) { + if (app.includes('problem')) { + const solutionApp = app.replace('problem', 'solution') + const testDir = path.join(solutionApp, 'tests') + const destTestDir = path.join(app, 'tests') + + if (exists(testDir)) { + // Remove existing test directory in problem app if it exists + if (exists(destTestDir)) { + await fs.promises.rm(destTestDir, { recursive: true, force: true }) + } + + // Copy the entire test directory from solution to problem + await fs.promises.cp(testDir, destTestDir, { recursive: true }) + } + } + } +} + async function updateTsconfig() { const tsconfig = { files: [], diff --git a/epicshop/package-lock.json b/epicshop/package-lock.json index 2e3d6a12f..27c3d7be6 100644 --- a/epicshop/package-lock.json +++ b/epicshop/package-lock.json @@ -8,7 +8,10 @@ "@epic-web/config": "^1.11.2", "@epic-web/workshop-app": "^4.28.2", "@epic-web/workshop-utils": "^4.28.2", - "execa": "^9.2.0" + "enquirer": "^2.4.1", + "execa": "^9.4.0", + "match-sorter": "^6.3.4", + "p-limit": "^6.1.0" } }, "node_modules/@adobe/css-tools": { @@ -4732,6 +4735,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -5918,6 +5930,31 @@ "node": ">= 0.8" } }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -6688,18 +6725,19 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/execa": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.2.0.tgz", - "integrity": "sha512-vpOyYg7UAVKLAWWtRS2gAdgkT7oJbCn0me3gmUmxZih4kd3MF/oo8kNTBTIbkO3yuuF5uB4ZCZfn8BOolITYhg==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.0.tgz", + "integrity": "sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==", + "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", - "human-signals": "^7.0.0", + "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", - "npm-run-path": "^5.2.0", + "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", @@ -6712,6 +6750,46 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/express": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", @@ -7579,9 +7657,10 @@ } }, "node_modules/human-signals": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-7.0.0.tgz", - "integrity": "sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", + "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } @@ -8316,6 +8395,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/md5-hex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-5.0.0.tgz", @@ -9903,6 +9992,21 @@ "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.2.tgz", "integrity": "sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==" }, + "node_modules/p-limit": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz", + "integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-queue": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz", @@ -10984,6 +11088,12 @@ "node": ">=10" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12750,6 +12860,18 @@ "node": ">=8" } }, + "node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoctocolors": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.0.2.tgz", diff --git a/epicshop/package.json b/epicshop/package.json index b57e2637c..62a90b6ef 100644 --- a/epicshop/package.json +++ b/epicshop/package.json @@ -2,12 +2,15 @@ "type": "module", "scripts": { "test:setup": "playwright install chromium --with-deps", - "test": "playwright test" + "test": "playwright test && node test.js" }, "dependencies": { "@epic-web/config": "^1.11.2", "@epic-web/workshop-app": "^4.28.2", "@epic-web/workshop-utils": "^4.28.2", - "execa": "^9.2.0" + "enquirer": "^2.4.1", + "execa": "^9.4.0", + "match-sorter": "^6.3.4", + "p-limit": "^6.1.0" } } diff --git a/epicshop/test.js b/epicshop/test.js new file mode 100644 index 000000000..226b58902 --- /dev/null +++ b/epicshop/test.js @@ -0,0 +1,272 @@ +import path from 'node:path' +import { performance } from 'perf_hooks' +import { fileURLToPath } from 'url' +import { + getApps, + getAppDisplayName, +} from '@epic-web/workshop-utils/apps.server' +import enquirer from 'enquirer' +import { execa } from 'execa' +import { matchSorter } from 'match-sorter' +import pLimit from 'p-limit' + +const { prompt } = enquirer + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +function captureOutput() { + const output = [] + return { + write: (chunk, streamType) => { + output.push({ chunk: chunk.toString(), streamType }) + }, + replay: () => { + for (const { chunk, streamType } of output) { + if (streamType === 'stderr') { + process.stderr.write(chunk) + } else { + process.stdout.write(chunk) + } + } + }, + hasOutput: () => output.length > 0, + } +} + +function printTestSummary(results) { + const label = '--- Test Summary ---' + console.log(`\n${label}`) + for (const [appPath, { result, duration }] of results) { + let emoji + switch (result) { + case 'Passed': + emoji = 'āœ…' + break + case 'Failed': + emoji = 'āŒ' + break + case 'Error': + emoji = 'šŸ’„' + break + case 'Incomplete': + emoji = 'ā³' + break + default: + emoji = 'ā“' + } + console.log(`${emoji} ${appPath} (${duration.toFixed(2)}s)`) + } + console.log(`${'-'.repeat(label.length)}\n`) +} + +async function main() { + const allApps = (await getApps()).filter((app) => app.test.type === 'script') + + let selectedApps + let additionalArgs = [] + + // Parse command-line arguments + const argIndex = process.argv.indexOf('--') + if (argIndex !== -1) { + additionalArgs = process.argv.slice(argIndex + 1) + process.argv = process.argv.slice(0, argIndex) + } + + if (process.argv[2]) { + const patterns = process.argv[2].toLowerCase().split(',') + selectedApps = allApps.filter((app) => { + const { exerciseNumber, stepNumber, type } = app + + return patterns.some((pattern) => { + let [patternExercise = '*', patternStep = '*', patternType = '*'] = + pattern.split('.') + + patternExercise ||= '*' + patternStep ||= '*' + patternType ||= '*' + + return ( + (patternExercise === '*' || + exerciseNumber === Number(patternExercise)) && + (patternStep === '*' || stepNumber === Number(patternStep)) && + (patternType === '*' || type.includes(patternType)) + ) + }) + }) + } else { + const displayNameMap = new Map( + allApps.map((app) => [getAppDisplayName(app, allApps), app]), + ) + const choices = displayNameMap.keys() + + const response = await prompt({ + type: 'autocomplete', + name: 'appDisplayNames', + message: 'Select apps to test:', + choices: ['All', ...choices], + multiple: true, + suggest: (input, choices) => { + return matchSorter(choices, input, { keys: ['name'] }) + }, + }) + + selectedApps = response.appDisplayNames.includes('All') + ? allApps + : response.appDisplayNames.map((appDisplayName) => + displayNameMap.get(appDisplayName), + ) + + // Update this block to use process.argv + const appPattern = + selectedApps.length === allApps.length + ? '*' + : selectedApps + .map((app) => `${app.exerciseNumber}.${app.stepNumber}.${app.type}`) + .join(',') + const additionalArgsString = + additionalArgs.length > 0 ? ` -- ${additionalArgs.join(' ')}` : '' + console.log(`\nā„¹ļø To skip the prompt next time, use this command:`) + console.log(`npm test -- ${appPattern}${additionalArgsString}\n`) + } + + if (selectedApps.length === 0) { + console.log('āš ļø No apps selected. Exiting.') + return + } + + if (selectedApps.length === 1) { + const app = selectedApps[0] + console.log(`šŸš€ Running tests for ${app.relativePath}\n\n`) + const startTime = performance.now() + try { + await execa('npm', ['run', 'test', '--silent', '--', ...additionalArgs], { + cwd: app.fullPath, + stdio: 'inherit', + env: { + ...process.env, + PORT: app.dev.portNumber, + }, + }) + const duration = (performance.now() - startTime) / 1000 + console.log( + `āœ… Finished tests for ${app.relativePath} (${duration.toFixed(2)}s)`, + ) + } catch { + const duration = (performance.now() - startTime) / 1000 + console.error( + `āŒ Tests failed for ${app.relativePath} (${duration.toFixed(2)}s)`, + ) + process.exit(1) + } + } else { + const limit = pLimit(1) + let hasFailures = false + const runningProcesses = new Map() + let isShuttingDown = false + const results = new Map() + + const shutdownHandler = () => { + if (isShuttingDown) return + isShuttingDown = true + console.log('\nGracefully shutting down. Please wait...') + console.log('Outputting results of running tests:') + for (const [app, output] of runningProcesses.entries()) { + if (output.hasOutput()) { + console.log(`\nPartial results for ${app.relativePath}:\n\n`) + output.replay() + console.log('\n\n') + } else { + console.log(`ā„¹ļø No output captured for ${app.relativePath}`) + } + // Set result for incomplete tests + if (!results.has(app.relativePath)) { + results.set(app.relativePath, 'Incomplete') + } + } + printTestSummary(results) + // Allow some time for output to be written before exiting + setTimeout(() => process.exit(1), 100) + } + + process.on('SIGINT', shutdownHandler) + process.on('SIGTERM', shutdownHandler) + + const tasks = selectedApps.map((app) => + limit(async () => { + if (isShuttingDown) return + console.log(`šŸš€ Starting tests for ${app.relativePath}`) + const output = captureOutput() + runningProcesses.set(app, output) + const startTime = performance.now() + try { + const subprocess = execa( + 'npm', + ['run', 'test', '--silent', '--', ...additionalArgs], + { + cwd: path.join(__dirname, '..', app.relativePath), + reject: false, + env: { + ...process.env, + PORT: app.dev.portNumber, + }, + }, + ) + + subprocess.stdout.on('data', (chunk) => output.write(chunk, 'stdout')) + subprocess.stderr.on('data', (chunk) => output.write(chunk, 'stderr')) + + const { exitCode } = await subprocess + const duration = (performance.now() - startTime) / 1000 + + runningProcesses.delete(app) + + if (exitCode !== 0) { + hasFailures = true + console.error( + `\nāŒ Tests failed for ${app.relativePath} (${duration.toFixed(2)}s):\n\n`, + ) + output.replay() + console.log('\n\n') + results.set(app.relativePath, { result: 'Failed', duration }) + // Set result for incomplete tests + if (!results.has(app.relativePath)) { + results.set(app.relativePath, 'Incomplete') + } + } else { + console.log( + `āœ… Finished tests for ${app.relativePath} (${duration.toFixed(2)}s)`, + ) + results.set(app.relativePath, { result: 'Passed', duration }) + } + } catch (error) { + const duration = (performance.now() - startTime) / 1000 + runningProcesses.delete(app) + hasFailures = true + console.error( + `\nāŒ An error occurred while running tests for ${app.relativePath} (${duration.toFixed(2)}s):\n\n`, + ) + console.error(error.message) + output.replay() + console.log('\n\n') + results.set(app.relativePath, { result: 'Error', duration }) + } + }), + ) + + await Promise.all(tasks) + + // Print summary output + printTestSummary(results) + + if (hasFailures) { + process.exit(1) + } + } +} + +main().catch((error) => { + if (error) { + console.error('āŒ An error occurred:', error) + } + setTimeout(() => process.exit(1), 100) +}) diff --git a/exercises/04.code-splitting/01.problem.lazy/package.json b/exercises/04.code-splitting/01.problem.lazy/package.json index 958607d42..0cac993fc 100644 --- a/exercises/04.code-splitting/01.problem.lazy/package.json +++ b/exercises/04.code-splitting/01.problem.lazy/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@use-gesture/react": "^10.3.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", diff --git a/exercises/04.code-splitting/01.problem.lazy/tests/code-split.test.js b/exercises/04.code-splitting/01.problem.lazy/tests/code-split.test.js new file mode 100644 index 000000000..c275362d3 --- /dev/null +++ b/exercises/04.code-splitting/01.problem.lazy/tests/code-split.test.js @@ -0,0 +1,34 @@ +import { test, expect } from '@playwright/test' + +test('should load the globe and countries modules on demand', async ({ + page, +}) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + + await page.getByRole('checkbox', { name: /show globe/i }).click() + + await expect( + page.getByText(/loading/i), + 'šŸšØ the suspense fallback should show "loading" when showing the globe while the js is lazy loaded', + ).toBeVisible() + // Wait for the network to be idle after clicking the button + await page.waitForLoadState('networkidle') + + const jsRequests = await page.evaluate(() => + performance + .getEntriesByType('resource') + .filter((entry) => entry.initiatorType === 'script') + .map((entry) => entry.name), + ) + + // Check if any of the requests include 'globe' or 'countries' in their name + const hasGlobeOrCountriesModule = jsRequests.some( + (url) => url.includes('globe') || url.includes('countries'), + ) + + expect(hasGlobeOrCountriesModule).toBe( + true, + 'Expected to find a request for globe or countries module', + ) +}) diff --git a/exercises/04.code-splitting/01.problem.lazy/tests/playwright.config.js b/exercises/04.code-splitting/01.problem.lazy/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/04.code-splitting/01.problem.lazy/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/04.code-splitting/01.solution.lazy/package.json b/exercises/04.code-splitting/01.solution.lazy/package.json index ddaa7d405..b20b069b3 100644 --- a/exercises/04.code-splitting/01.solution.lazy/package.json +++ b/exercises/04.code-splitting/01.solution.lazy/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@use-gesture/react": "^10.3.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", diff --git a/exercises/04.code-splitting/01.solution.lazy/tests/code-split.test.js b/exercises/04.code-splitting/01.solution.lazy/tests/code-split.test.js new file mode 100644 index 000000000..c275362d3 --- /dev/null +++ b/exercises/04.code-splitting/01.solution.lazy/tests/code-split.test.js @@ -0,0 +1,34 @@ +import { test, expect } from '@playwright/test' + +test('should load the globe and countries modules on demand', async ({ + page, +}) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + + await page.getByRole('checkbox', { name: /show globe/i }).click() + + await expect( + page.getByText(/loading/i), + 'šŸšØ the suspense fallback should show "loading" when showing the globe while the js is lazy loaded', + ).toBeVisible() + // Wait for the network to be idle after clicking the button + await page.waitForLoadState('networkidle') + + const jsRequests = await page.evaluate(() => + performance + .getEntriesByType('resource') + .filter((entry) => entry.initiatorType === 'script') + .map((entry) => entry.name), + ) + + // Check if any of the requests include 'globe' or 'countries' in their name + const hasGlobeOrCountriesModule = jsRequests.some( + (url) => url.includes('globe') || url.includes('countries'), + ) + + expect(hasGlobeOrCountriesModule).toBe( + true, + 'Expected to find a request for globe or countries module', + ) +}) diff --git a/exercises/04.code-splitting/01.solution.lazy/tests/playwright.config.js b/exercises/04.code-splitting/01.solution.lazy/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/04.code-splitting/01.solution.lazy/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/04.code-splitting/02.problem.eager/package.json b/exercises/04.code-splitting/02.problem.eager/package.json index 1e546ca94..0dc992cd1 100644 --- a/exercises/04.code-splitting/02.problem.eager/package.json +++ b/exercises/04.code-splitting/02.problem.eager/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@use-gesture/react": "^10.3.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", diff --git a/exercises/04.code-splitting/02.problem.eager/tests/playwright.config.js b/exercises/04.code-splitting/02.problem.eager/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/04.code-splitting/02.problem.eager/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/04.code-splitting/02.problem.eager/tests/todo.test.js b/exercises/04.code-splitting/02.problem.eager/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/04.code-splitting/02.problem.eager/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/04.code-splitting/02.solution.eager/package.json b/exercises/04.code-splitting/02.solution.eager/package.json index bf2c69db1..5830047ce 100644 --- a/exercises/04.code-splitting/02.solution.eager/package.json +++ b/exercises/04.code-splitting/02.solution.eager/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@use-gesture/react": "^10.3.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", diff --git a/exercises/04.code-splitting/02.solution.eager/tests/playwright.config.js b/exercises/04.code-splitting/02.solution.eager/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/04.code-splitting/02.solution.eager/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/04.code-splitting/02.solution.eager/tests/todo.test.js b/exercises/04.code-splitting/02.solution.eager/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/04.code-splitting/02.solution.eager/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/04.code-splitting/03.problem.transition/package.json b/exercises/04.code-splitting/03.problem.transition/package.json index bebb6dfc9..55867c61e 100644 --- a/exercises/04.code-splitting/03.problem.transition/package.json +++ b/exercises/04.code-splitting/03.problem.transition/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@use-gesture/react": "^10.3.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", diff --git a/exercises/04.code-splitting/03.problem.transition/tests/playwright.config.js b/exercises/04.code-splitting/03.problem.transition/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/04.code-splitting/03.problem.transition/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/04.code-splitting/03.problem.transition/tests/todo.test.js b/exercises/04.code-splitting/03.problem.transition/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/04.code-splitting/03.problem.transition/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/04.code-splitting/03.solution.transition/package.json b/exercises/04.code-splitting/03.solution.transition/package.json index 088e693f8..6954326f2 100644 --- a/exercises/04.code-splitting/03.solution.transition/package.json +++ b/exercises/04.code-splitting/03.solution.transition/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@use-gesture/react": "^10.3.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", diff --git a/exercises/04.code-splitting/03.solution.transition/tests/playwright.config.js b/exercises/04.code-splitting/03.solution.transition/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/04.code-splitting/03.solution.transition/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/04.code-splitting/03.solution.transition/tests/todo.test.js b/exercises/04.code-splitting/03.solution.transition/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/04.code-splitting/03.solution.transition/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/05.calculations/01.problem.use-memo/package.json b/exercises/05.calculations/01.problem.use-memo/package.json index 815d529eb..93481085d 100644 --- a/exercises/05.calculations/01.problem.use-memo/package.json +++ b/exercises/05.calculations/01.problem.use-memo/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -23,6 +24,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/05.calculations/01.problem.use-memo/tests/playwright.config.js b/exercises/05.calculations/01.problem.use-memo/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/05.calculations/01.problem.use-memo/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/05.calculations/01.problem.use-memo/tests/todo.test.js b/exercises/05.calculations/01.problem.use-memo/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/05.calculations/01.problem.use-memo/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/05.calculations/01.solution.use-memo/package.json b/exercises/05.calculations/01.solution.use-memo/package.json index 4224026c0..6fefaf60d 100644 --- a/exercises/05.calculations/01.solution.use-memo/package.json +++ b/exercises/05.calculations/01.solution.use-memo/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -23,6 +24,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/05.calculations/01.solution.use-memo/tests/playwright.config.js b/exercises/05.calculations/01.solution.use-memo/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/05.calculations/01.solution.use-memo/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/05.calculations/01.solution.use-memo/tests/todo.test.js b/exercises/05.calculations/01.solution.use-memo/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/05.calculations/01.solution.use-memo/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/05.calculations/02.problem.worker/package.json b/exercises/05.calculations/02.problem.worker/package.json index d4e170fda..1b50db4f5 100644 --- a/exercises/05.calculations/02.problem.worker/package.json +++ b/exercises/05.calculations/02.problem.worker/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -23,6 +24,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/05.calculations/02.problem.worker/tests/playwright.config.js b/exercises/05.calculations/02.problem.worker/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/05.calculations/02.problem.worker/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/05.calculations/02.problem.worker/tests/todo.test.js b/exercises/05.calculations/02.problem.worker/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/05.calculations/02.problem.worker/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/05.calculations/02.solution.worker/package.json b/exercises/05.calculations/02.solution.worker/package.json index 499c6e392..e3a6a7547 100644 --- a/exercises/05.calculations/02.solution.worker/package.json +++ b/exercises/05.calculations/02.solution.worker/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -23,6 +24,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/05.calculations/02.solution.worker/tests/playwright.config.js b/exercises/05.calculations/02.solution.worker/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/05.calculations/02.solution.worker/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/05.calculations/02.solution.worker/tests/todo.test.js b/exercises/05.calculations/02.solution.worker/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/05.calculations/02.solution.worker/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/05.calculations/03.problem.async/package.json b/exercises/05.calculations/03.problem.async/package.json index b41a34603..430b3a815 100644 --- a/exercises/05.calculations/03.problem.async/package.json +++ b/exercises/05.calculations/03.problem.async/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -23,6 +24,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/05.calculations/03.problem.async/tests/playwright.config.js b/exercises/05.calculations/03.problem.async/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/05.calculations/03.problem.async/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/05.calculations/03.problem.async/tests/todo.test.js b/exercises/05.calculations/03.problem.async/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/05.calculations/03.problem.async/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/05.calculations/03.solution.async/package.json b/exercises/05.calculations/03.solution.async/package.json index 9c89652b6..bc1115cf5 100644 --- a/exercises/05.calculations/03.solution.async/package.json +++ b/exercises/05.calculations/03.solution.async/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -23,6 +24,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/05.calculations/03.solution.async/tests/playwright.config.js b/exercises/05.calculations/03.solution.async/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/05.calculations/03.solution.async/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/05.calculations/03.solution.async/tests/todo.test.js b/exercises/05.calculations/03.solution.async/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/05.calculations/03.solution.async/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/06.rerenders/01.problem.memo/package.json b/exercises/06.rerenders/01.problem.memo/package.json index 410690066..4b19c8cfc 100644 --- a/exercises/06.rerenders/01.problem.memo/package.json +++ b/exercises/06.rerenders/01.problem.memo/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -18,6 +19,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/06.rerenders/01.problem.memo/tests/playwright.config.js b/exercises/06.rerenders/01.problem.memo/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/06.rerenders/01.problem.memo/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/06.rerenders/01.problem.memo/tests/todo.test.js b/exercises/06.rerenders/01.problem.memo/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/06.rerenders/01.problem.memo/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/06.rerenders/01.solution.memo/package.json b/exercises/06.rerenders/01.solution.memo/package.json index eefc27fe0..6a75644a4 100644 --- a/exercises/06.rerenders/01.solution.memo/package.json +++ b/exercises/06.rerenders/01.solution.memo/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -18,6 +19,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/06.rerenders/01.solution.memo/tests/playwright.config.js b/exercises/06.rerenders/01.solution.memo/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/06.rerenders/01.solution.memo/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/06.rerenders/01.solution.memo/tests/todo.test.js b/exercises/06.rerenders/01.solution.memo/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/06.rerenders/01.solution.memo/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/06.rerenders/02.problem.comparator/package.json b/exercises/06.rerenders/02.problem.comparator/package.json index 1ed3ec8c1..234565791 100644 --- a/exercises/06.rerenders/02.problem.comparator/package.json +++ b/exercises/06.rerenders/02.problem.comparator/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -18,6 +19,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/06.rerenders/02.problem.comparator/tests/playwright.config.js b/exercises/06.rerenders/02.problem.comparator/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/06.rerenders/02.problem.comparator/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/06.rerenders/02.problem.comparator/tests/todo.test.js b/exercises/06.rerenders/02.problem.comparator/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/06.rerenders/02.problem.comparator/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/06.rerenders/02.solution.comparator/package.json b/exercises/06.rerenders/02.solution.comparator/package.json index a6928b5a7..be64df7c0 100644 --- a/exercises/06.rerenders/02.solution.comparator/package.json +++ b/exercises/06.rerenders/02.solution.comparator/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -18,6 +19,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/06.rerenders/02.solution.comparator/tests/playwright.config.js b/exercises/06.rerenders/02.solution.comparator/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/06.rerenders/02.solution.comparator/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/06.rerenders/02.solution.comparator/tests/todo.test.js b/exercises/06.rerenders/02.solution.comparator/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/06.rerenders/02.solution.comparator/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/06.rerenders/03.problem.primitives/package.json b/exercises/06.rerenders/03.problem.primitives/package.json index c254b995f..a03c069db 100644 --- a/exercises/06.rerenders/03.problem.primitives/package.json +++ b/exercises/06.rerenders/03.problem.primitives/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -18,6 +19,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/06.rerenders/03.problem.primitives/tests/playwright.config.js b/exercises/06.rerenders/03.problem.primitives/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/06.rerenders/03.problem.primitives/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/06.rerenders/03.problem.primitives/tests/todo.test.js b/exercises/06.rerenders/03.problem.primitives/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/06.rerenders/03.problem.primitives/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/06.rerenders/03.solution.primitives/package.json b/exercises/06.rerenders/03.solution.primitives/package.json index e6c25b8eb..a19c184a5 100644 --- a/exercises/06.rerenders/03.solution.primitives/package.json +++ b/exercises/06.rerenders/03.solution.primitives/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "comlink": "^4.4.1", @@ -18,6 +19,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/06.rerenders/03.solution.primitives/tests/playwright.config.js b/exercises/06.rerenders/03.solution.primitives/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/06.rerenders/03.solution.primitives/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/06.rerenders/03.solution.primitives/tests/todo.test.js b/exercises/06.rerenders/03.solution.primitives/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/06.rerenders/03.solution.primitives/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/07.windowing/01.problem.virtualizer/package.json b/exercises/07.windowing/01.problem.virtualizer/package.json index fec8a9058..0debf0fa1 100644 --- a/exercises/07.windowing/01.problem.virtualizer/package.json +++ b/exercises/07.windowing/01.problem.virtualizer/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@tanstack/react-virtual": "^3.1.2", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/07.windowing/01.problem.virtualizer/tests/playwright.config.js b/exercises/07.windowing/01.problem.virtualizer/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/07.windowing/01.problem.virtualizer/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/07.windowing/01.problem.virtualizer/tests/todo.test.js b/exercises/07.windowing/01.problem.virtualizer/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/07.windowing/01.problem.virtualizer/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/exercises/07.windowing/01.solution.virtualizer/package.json b/exercises/07.windowing/01.solution.virtualizer/package.json index 69c146aca..c0eefd040 100644 --- a/exercises/07.windowing/01.solution.virtualizer/package.json +++ b/exercises/07.windowing/01.solution.virtualizer/package.json @@ -11,7 +11,8 @@ "scripts": { "dev": "vite --host", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "test": "playwright test --config=./tests/playwright.config.js" }, "dependencies": { "@tanstack/react-virtual": "^3.1.2", @@ -24,6 +25,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/exercises/07.windowing/01.solution.virtualizer/tests/playwright.config.js b/exercises/07.windowing/01.solution.virtualizer/tests/playwright.config.js new file mode 100644 index 000000000..90b668373 --- /dev/null +++ b/exercises/07.windowing/01.solution.virtualizer/tests/playwright.config.js @@ -0,0 +1,41 @@ +import os from 'os' +import path from 'path' +import { defineConfig, devices } from '@playwright/test' + +const PORT = process.env.PORT || '3000' + +const tmpDir = path.join(os.tmpdir(), 'epicreact-performance') + +export default defineConfig({ + timeout: 10000 * (process.env.CI ? 10 : 1), + expect: { + timeout: 5000 * (process.env.CI ? 10 : 1), + }, + outputDir: path.join(tmpDir, 'playwright-test-output'), + reporter: [ + [ + 'html', + { open: 'never', outputFolder: path.join(tmpDir, 'playwright-report') }, + ], + ], + use: { + baseURL: `http://localhost:${PORT}/`, + trace: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + webServer: { + command: 'npm run dev', + port: Number(PORT), + reuseExistingServer: !process.env.CI, + stdout: 'pipe', + stderr: 'pipe', + env: { PORT }, + }, +}) diff --git a/exercises/07.windowing/01.solution.virtualizer/tests/todo.test.js b/exercises/07.windowing/01.solution.virtualizer/tests/todo.test.js new file mode 100644 index 000000000..89017c5c3 --- /dev/null +++ b/exercises/07.windowing/01.solution.virtualizer/tests/todo.test.js @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test' + +test('todo', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + // TODO: finish this test +}) diff --git a/package-lock.json b/package-lock.json index 550499838..0b3479790 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", @@ -70,6 +71,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", @@ -95,6 +97,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", @@ -120,6 +123,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", @@ -145,6 +149,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", @@ -170,6 +175,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/d3-geo": "^3.1.0", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", @@ -194,6 +200,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -216,6 +223,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -238,6 +246,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -260,6 +269,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -282,6 +292,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -304,6 +315,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -326,6 +338,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -348,6 +361,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -370,6 +384,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -392,6 +407,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -414,6 +430,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -436,6 +453,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -459,6 +477,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", @@ -482,6 +501,7 @@ }, "devDependencies": { "@epic-web/config": "^1.5.4", + "@playwright/test": "^1.47.2", "@types/node": "^20.9.1", "@types/react": "npm:types-react@19.0.0-alpha.5", "@types/react-dom": "npm:types-react-dom@19.0.0-alpha.5", diff --git a/tsconfig.json b/tsconfig.json index b4cc123c6..9305238d4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,66 +1,68 @@ { - "files": [], - "exclude": ["node_modules"], - "references": [ - { - "path": "exercises/04.code-splitting/01.problem.lazy" - }, - { - "path": "exercises/04.code-splitting/01.solution.lazy" - }, - { - "path": "exercises/04.code-splitting/02.problem.eager" - }, - { - "path": "exercises/04.code-splitting/02.solution.eager" - }, - { - "path": "exercises/04.code-splitting/03.problem.transition" - }, - { - "path": "exercises/04.code-splitting/03.solution.transition" - }, - { - "path": "exercises/05.calculations/01.problem.use-memo" - }, - { - "path": "exercises/05.calculations/01.solution.use-memo" - }, - { - "path": "exercises/05.calculations/02.problem.worker" - }, - { - "path": "exercises/05.calculations/02.solution.worker" - }, - { - "path": "exercises/05.calculations/03.problem.async" - }, - { - "path": "exercises/05.calculations/03.solution.async" - }, - { - "path": "exercises/06.rerenders/01.problem.memo" - }, - { - "path": "exercises/06.rerenders/01.solution.memo" - }, - { - "path": "exercises/06.rerenders/02.problem.comparator" - }, - { - "path": "exercises/06.rerenders/02.solution.comparator" - }, - { - "path": "exercises/06.rerenders/03.problem.primitives" - }, - { - "path": "exercises/06.rerenders/03.solution.primitives" - }, - { - "path": "exercises/07.windowing/01.problem.virtualizer" - }, - { - "path": "exercises/07.windowing/01.solution.virtualizer" - } - ] + "files": [], + "exclude": [ + "node_modules" + ], + "references": [ + { + "path": "exercises/04.code-splitting/01.problem.lazy" + }, + { + "path": "exercises/04.code-splitting/01.solution.lazy" + }, + { + "path": "exercises/04.code-splitting/02.problem.eager" + }, + { + "path": "exercises/04.code-splitting/02.solution.eager" + }, + { + "path": "exercises/04.code-splitting/03.problem.transition" + }, + { + "path": "exercises/04.code-splitting/03.solution.transition" + }, + { + "path": "exercises/05.calculations/01.problem.use-memo" + }, + { + "path": "exercises/05.calculations/01.solution.use-memo" + }, + { + "path": "exercises/05.calculations/02.problem.worker" + }, + { + "path": "exercises/05.calculations/02.solution.worker" + }, + { + "path": "exercises/05.calculations/03.problem.async" + }, + { + "path": "exercises/05.calculations/03.solution.async" + }, + { + "path": "exercises/06.rerenders/01.problem.memo" + }, + { + "path": "exercises/06.rerenders/01.solution.memo" + }, + { + "path": "exercises/06.rerenders/02.problem.comparator" + }, + { + "path": "exercises/06.rerenders/02.solution.comparator" + }, + { + "path": "exercises/06.rerenders/03.problem.primitives" + }, + { + "path": "exercises/06.rerenders/03.solution.primitives" + }, + { + "path": "exercises/07.windowing/01.problem.virtualizer" + }, + { + "path": "exercises/07.windowing/01.solution.virtualizer" + } + ] }