From 389dfef1d4712760167dccdf66a549fe36786dd7 Mon Sep 17 00:00:00 2001 From: Geethegreat <86944224+Geethegreat@users.noreply.github.com> Date: Mon, 25 May 2026 23:17:45 +0530 Subject: [PATCH 1/9] Add playwright CI workflow --- .github/workflows/e2e.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000000..2fedb2adba --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,27 @@ +name: E2E Tests +on: + pull_request: + branches: + - develop + +jobs: + e2e: + name: End-to-end tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js + uses: actions/setup-node@v1 + with: + node-version: '18.20.x' + - run: npm install + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + - name: Start the app + run: npm run start & + env: + NODE_ENV: test + - name: Wait for app to be ready + run: npx wait-on http://localhost:8000 --timeout 60000 + - name: Run E2E tests + run: npx playwright test \ No newline at end of file From 617c4e418182c0c86991ef0c70308c285d41cd00 Mon Sep 17 00:00:00 2001 From: Geethegreat <86944224+Geethegreat@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:28:27 +0530 Subject: [PATCH 2/9] Add Playwright sketch execution test --- playwright.config.ts | 18 ++++ tests/playwright/editor.spec.ts | 153 ++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 playwright.config.ts create mode 100644 tests/playwright/editor.spec.ts diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000..145cc5edad --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,18 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests/playwright', + testMatch: '**/*.spec.ts', + timeout: 30000, + + use: { + baseURL: 'http://localhost:8000', + headless: true, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + } + ] +}); diff --git a/tests/playwright/editor.spec.ts b/tests/playwright/editor.spec.ts new file mode 100644 index 0000000000..440deed53d --- /dev/null +++ b/tests/playwright/editor.spec.ts @@ -0,0 +1,153 @@ +import { test, expect, Page } from '@playwright/test'; + +async function dismissCookies(page: Page) { + try { + await page.waitForSelector('button', { timeout: 3_000 }); + await page.evaluate(() => { + const btn = Array.from(document.querySelectorAll('button')).find((b) => + /allow all|allow essential/i.test(b.textContent ?? '') + ) as HTMLElement | undefined; + btn?.click(); + }); + await page.waitForTimeout(400); + } catch { + /* no banner */ + } +} + +test.describe('p5.js Editor – Playwright E2E', () => { + test('editor loads and has a sketch iframe', async ({ page }) => { + await page.goto('http://localhost:8000', { + waitUntil: 'domcontentloaded', + timeout: 30_000 + }); + await dismissCookies(page); + await page.evaluate(() => { + (document.querySelector( + '[aria-label="Play sketch"]' + ) as HTMLElement)?.click(); + }); + const iframeHandle = await page.waitForSelector('iframe', { + timeout: 15_000 + }); + expect(iframeHandle).toBeTruthy(); + }); + + test('can access iframe content frame', async ({ page }) => { + await page.goto('http://localhost:8000'); + await dismissCookies(page); + await page.waitForSelector('iframe'); + const body = page.frameLocator('iframe').first().locator('body'); + await expect(body).toBeAttached({ timeout: 10_000 }); + }); + + test('run button triggers sketch in iframe', async ({ page }) => { + await page.goto('http://localhost:8000'); + await dismissCookies(page); + await page.evaluate(() => { + (document.querySelector( + '[aria-label="Play sketch"]' + ) as HTMLElement)?.click(); + }); + await page.waitForSelector('iframe', { timeout: 10_000 }); + const iframeSrc = await page.locator('iframe').getAttribute('src'); + console.log('iframe src:', iframeSrc); + expect(iframeSrc).toBeTruthy(); + await expect(page.locator('iframe')).toBeVisible({ timeout: 10_000 }); + }); + + test.skip('sketch execution via postMessage', async () => { + // FINDING: postMessage interception via page.evaluate() returns empty. + // The sketch iframe (localhost:8002) sends messages via window.parent.parent + // but these do not surface in Playwright's main page context. + // Testing sketch output would require CDP or a dedicated message relay + // — a candidate for GSoC implementation. + }); + + test('sketch console.log appears in editor console', async ({ page }) => { + await page.goto('http://localhost:8000'); + + await dismissCookies(page); + + await page.waitForFunction(() => { + const wrapper = document.querySelector('.CodeMirror') as any; + return !!wrapper?.CodeMirror; + }); + + await page.evaluate( + (newCode) => { + const cm = (document.querySelector('.CodeMirror') as any)?.CodeMirror; + + if (!cm) { + throw new Error('CodeMirror not found'); + } + + cm.setValue(newCode); + cm.refresh(); + + const root = document.querySelector('#root') as any; + + const fiberKey = Object.keys(root).find((k) => + k.startsWith('__reactContainer') + ); + + let node = root[fiberKey]; + let store: any = null; + + while (node) { + if (node.memoizedProps?.store) { + store = node.memoizedProps.store; + break; + } + + node = node.child; + } + + if (!store) { + throw new Error('Redux store not found'); + } + + const selectedFile = store + .getState() + .files.find((f: any) => f.isSelectedFile); + + if (!selectedFile) { + throw new Error('Selected file not found'); + } + + store.dispatch({ + type: 'UPDATE_FILE_CONTENT', + id: selectedFile.id, + content: newCode + }); + }, + ` +function setup() { + createCanvas(400, 400); +} + +function draw() { + background(220); + console.log('hi from sketch'); + noLoop(); +} +` + ); + + await page.waitForTimeout(1000); + + await page.locator('#play-sketch').click(); + + const openConsoleButton = page.locator('[aria-label="Open console"]'); + + if (await openConsoleButton.isVisible()) { + await openConsoleButton.click(); + } + + await expect + .poll(() => page.locator('.preview-console__messages').textContent(), { + timeout: 15000 + }) + .toContain('hi from sketch'); + }); +}); From 630199e3dd931b5430f628596d23319e10efa017 Mon Sep 17 00:00:00 2001 From: Geethegreat <86944224+Geethegreat@users.noreply.github.com> Date: Sat, 13 Jun 2026 17:56:25 +0530 Subject: [PATCH 3/9] improve e2e CI startup --- .github/workflows/e2e.yml | 99 ++++++++++++++++++++++++++++++++++++--- playwright.config.ts | 2 +- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2fedb2adba..7901a3a13d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,4 +1,5 @@ name: E2E Tests + on: pull_request: branches: @@ -8,20 +9,104 @@ jobs: e2e: name: End-to-end tests runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: '18.20.x' - - run: npm install + cache: 'npm' + + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.10.0 + with: + mongodb-version: '6.0' + + - name: Create .env file + run: | + cat > .env << 'EOF' + API_URL=/editor + CORS_ALLOW_LOCALHOST=true + TRANSLATIONS_ENABLED=true + UI_ACCESS_TOKEN_ENABLED=false + UPLOAD_LIMIT=250000000 + + PORT=8000 + PREVIEW_PORT=8002 + EDITOR_URL=http://localhost:8000 + PREVIEW_URL=http://localhost:8002 + + MONGO_URL=mongodb://localhost:27017/p5js-web-editor + + SESSION_SECRET=ci-session-secret + EMAIL_VERIFY_SECRET_TOKEN=ci-verify-token + + # These accounts are used by the app to serve examples. + # Real values not needed for E2E tests — dummy strings prevent crashes. + EMAIL_SENDER=ci@example.com + EXAMPLE_USER_EMAIL=examples@p5js.org + EXAMPLE_USER_PASSWORD=hellop5js + GG_EXAMPLES_USERNAME=generativedesign + GG_EXAMPLES_EMAIL=benedikt.gross@generative-gestaltung.de + GG_EXAMPLES_PASS=generativedesign + ML5_LIBRARY_USERNAME=ml5 + ML5_LIBRARY_EMAIL=examples@ml5js.org + ML5_LIBRARY_PASS=helloml5 + + # Mailgun — non-empty string required to pass the startup check in + # server/utils/mail.ts. No emails are sent during E2E tests. + MAILGUN_KEY=dummy-mailgun-key + MAILGUN_DOMAIN=dummy.mailgun.org + + # AWS S3 — non-empty strings required. No file uploads in E2E tests. + AWS_ACCESS_KEY=dummy-aws-access-key + AWS_SECRET_KEY=dummy-aws-secret-key + AWS_REGION=us-east-1 + S3_BUCKET=dummy-bucket + S3_BUCKET_URL_BASE=https://dummy-bucket.s3.amazonaws.com + + # GitHub OAuth — not tested in E2E suite, dummy values prevent crash. + GITHUB_ID=dummy-github-id + GITHUB_SECRET=dummy-github-secret + + # Google OAuth — not tested in E2E suite, dummy values prevent crash. + GOOGLE_ID=dummy-google-id + GOOGLE_SECRET=dummy-google-secret + EOF + + - name: Install dependencies + run: npm install + - name: Install Playwright browsers run: npx playwright install --with-deps chromium + - name: Start the app run: npm run start & - env: - NODE_ENV: test + - name: Wait for app to be ready - run: npx wait-on http://localhost:8000 --timeout 60000 + run: | + for i in $(seq 1 120); do + if curl -sf http://localhost:8000 | grep -q "p5"; then + echo "App fully ready" + exit 0 + fi + + echo "Still bundling... $i/120" + sleep 5 + done + + echo "App did not become ready" + exit 1 + - name: Run E2E tests - run: npx playwright test \ No newline at end of file + run: npx playwright test --config=playwright.config.ts + + - name: Upload Playwright report on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts index 145cc5edad..a9d6403da8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,7 +3,7 @@ import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests/playwright', testMatch: '**/*.spec.ts', - timeout: 30000, + timeout: 120_000, use: { baseURL: 'http://localhost:8000', From 85fbbd977d0caddd3336ae5033499377b92bee57 Mon Sep 17 00:00:00 2001 From: Geethegreat <86944224+Geethegreat@users.noreply.github.com> Date: Sat, 13 Jun 2026 18:16:05 +0530 Subject: [PATCH 4/9] add playwright dependency --- package-lock.json | 74 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 75 insertions(+) diff --git a/package-lock.json b/package-lock.json index a0757fe864..3f30fb3c58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,6 +147,7 @@ "@babel/preset-env": "^7.14.7", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.27.1", + "@playwright/test": "^1.60.0", "@storybook/addon-actions": "^7.6.8", "@storybook/addon-docs": "^7.6.8", "@storybook/addon-essentials": "^7.6.8", @@ -7860,6 +7861,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", @@ -30296,6 +30313,38 @@ "node": ">=6" } }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/please-upgrade-node": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", @@ -42291,6 +42340,15 @@ "dev": true, "optional": true }, + "@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "requires": { + "playwright": "1.60.0" + } + }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz", @@ -58551,6 +58609,22 @@ "find-up": "^3.0.0" } }, + "playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.60.0" + } + }, + "playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true + }, "please-upgrade-node": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", diff --git a/package.json b/package.json index 2a8c3ca370..292e1c33e6 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "@babel/preset-env": "^7.14.7", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.27.1", + "@playwright/test": "^1.60.0", "@storybook/addon-actions": "^7.6.8", "@storybook/addon-docs": "^7.6.8", "@storybook/addon-essentials": "^7.6.8", From 97b5b6ecd7e9571f0c22d0291d7e928877f21c3c Mon Sep 17 00:00:00 2001 From: krishnageeth <86944224+Geethegreat@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:02:15 +0530 Subject: [PATCH 5/9] Update .github/workflows/e2e.yml Add new environmnet for e2e rather than using staging. Co-authored-by: Claire Peng <128436909+clairep94@users.noreply.github.com> --- .github/workflows/e2e.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 7901a3a13d..4e1da13077 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -8,6 +8,7 @@ on: jobs: e2e: name: End-to-end tests + environment: e2e-tests runs-on: ubuntu-latest steps: From 666fb320d67afc9fed8763bd6b2c252411b54152 Mon Sep 17 00:00:00 2001 From: krishnageeth <86944224+Geethegreat@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:04:10 +0530 Subject: [PATCH 6/9] Update .github/workflows/e2e.yml replace npm install with npm ci Co-authored-by: Claire Peng <128436909+clairep94@users.noreply.github.com> --- .github/workflows/e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4e1da13077..1490395b08 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -78,7 +78,7 @@ jobs: EOF - name: Install dependencies - run: npm install + run: npm ci - name: Install Playwright browsers run: npx playwright install --with-deps chromium From eb569d9945403f399db9be559240dbbe377287c5 Mon Sep 17 00:00:00 2001 From: krishnageeth <86944224+Geethegreat@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:05:01 +0530 Subject: [PATCH 7/9] Update playwright.config.ts add white space Co-authored-by: Claire Peng <128436909+clairep94@users.noreply.github.com> --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index a9d6403da8..130a0b1c98 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -3,7 +3,7 @@ import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests/playwright', testMatch: '**/*.spec.ts', - timeout: 120_000, + timeout: 120_000, use: { baseURL: 'http://localhost:8000', From 44fb350accd74a9ef674a409aa9211cb8617adf3 Mon Sep 17 00:00:00 2001 From: Geethegreat <86944224+Geethegreat@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:37:53 +0530 Subject: [PATCH 8/9] apply remaining review suggestions --- .env.e2e | 39 ++++++ .github/workflows/e2e.yml | 58 +-------- .gitignore | 5 + package.json | 2 + playwright.config.ts | 4 +- tests/playwright/editor.spec.ts | 214 +++++++++++++------------------- 6 files changed, 137 insertions(+), 185 deletions(-) create mode 100644 .env.e2e diff --git a/.env.e2e b/.env.e2e new file mode 100644 index 0000000000..dbf05c988b --- /dev/null +++ b/.env.e2e @@ -0,0 +1,39 @@ +API_URL=/editor +CORS_ALLOW_LOCALHOST=true +TRANSLATIONS_ENABLED=true +UI_ACCESS_TOKEN_ENABLED=false +UPLOAD_LIMIT=250000000 +PORT=8000 +PREVIEW_PORT=8002 +EDITOR_URL=http://localhost:8000 +PREVIEW_URL=http://localhost:8002 +MONGO_URL=mongodb://localhost:27017/p5js-web-editor +SESSION_SECRET=ci-session-secret +EMAIL_VERIFY_SECRET_TOKEN=ci-verify-token +# These accounts are used by the app to serve examples. +# Real values not needed for E2E tests — dummy strings prevent crashes. +EMAIL_SENDER=ci@example.com +EXAMPLE_USER_EMAIL=examples@p5js.org +EXAMPLE_USER_PASSWORD=hellop5js +GG_EXAMPLES_USERNAME=generativedesign +GG_EXAMPLES_EMAIL=benedikt.gross@generative-gestaltung.de +GG_EXAMPLES_PASS=generativedesign +ML5_LIBRARY_USERNAME=ml5 +ML5_LIBRARY_EMAIL=examples@ml5js.org +ML5_LIBRARY_PASS=helloml5 +# Mailgun — non-empty string required to pass the startup check in +# server/utils/mail.ts. No emails are sent during E2E tests. +MAILGUN_KEY=dummy-mailgun-key +MAILGUN_DOMAIN=dummy.mailgun.org +# AWS S3 — non-empty strings required. No file uploads in E2E tests. +AWS_ACCESS_KEY=dummy-aws-access-key +AWS_SECRET_KEY=dummy-aws-secret-key +AWS_REGION=us-east-1 +S3_BUCKET=dummy-bucket +S3_BUCKET_URL_BASE=https://dummy-bucket.s3.amazonaws.com +# GitHub OAuth — not tested in E2E suite, dummy values prevent crash. +GITHUB_ID=dummy-github-id +GITHUB_SECRET=dummy-github-secret +# Google OAuth — not tested in E2E suite, dummy values prevent crash. +GOOGLE_ID=dummy-google-id +GOOGLE_SECRET=dummy-google-secret diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 1490395b08..a86f455ebc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -26,56 +26,7 @@ jobs: mongodb-version: '6.0' - name: Create .env file - run: | - cat > .env << 'EOF' - API_URL=/editor - CORS_ALLOW_LOCALHOST=true - TRANSLATIONS_ENABLED=true - UI_ACCESS_TOKEN_ENABLED=false - UPLOAD_LIMIT=250000000 - - PORT=8000 - PREVIEW_PORT=8002 - EDITOR_URL=http://localhost:8000 - PREVIEW_URL=http://localhost:8002 - - MONGO_URL=mongodb://localhost:27017/p5js-web-editor - - SESSION_SECRET=ci-session-secret - EMAIL_VERIFY_SECRET_TOKEN=ci-verify-token - - # These accounts are used by the app to serve examples. - # Real values not needed for E2E tests — dummy strings prevent crashes. - EMAIL_SENDER=ci@example.com - EXAMPLE_USER_EMAIL=examples@p5js.org - EXAMPLE_USER_PASSWORD=hellop5js - GG_EXAMPLES_USERNAME=generativedesign - GG_EXAMPLES_EMAIL=benedikt.gross@generative-gestaltung.de - GG_EXAMPLES_PASS=generativedesign - ML5_LIBRARY_USERNAME=ml5 - ML5_LIBRARY_EMAIL=examples@ml5js.org - ML5_LIBRARY_PASS=helloml5 - - # Mailgun — non-empty string required to pass the startup check in - # server/utils/mail.ts. No emails are sent during E2E tests. - MAILGUN_KEY=dummy-mailgun-key - MAILGUN_DOMAIN=dummy.mailgun.org - - # AWS S3 — non-empty strings required. No file uploads in E2E tests. - AWS_ACCESS_KEY=dummy-aws-access-key - AWS_SECRET_KEY=dummy-aws-secret-key - AWS_REGION=us-east-1 - S3_BUCKET=dummy-bucket - S3_BUCKET_URL_BASE=https://dummy-bucket.s3.amazonaws.com - - # GitHub OAuth — not tested in E2E suite, dummy values prevent crash. - GITHUB_ID=dummy-github-id - GITHUB_SECRET=dummy-github-secret - - # Google OAuth — not tested in E2E suite, dummy values prevent crash. - GOOGLE_ID=dummy-google-id - GOOGLE_SECRET=dummy-google-secret - EOF + run: cp .env.e2e .env - name: Install dependencies run: npm ci @@ -102,7 +53,9 @@ jobs: exit 1 - name: Run E2E tests - run: npx playwright test --config=playwright.config.ts + run: npm run e2e:ci + env: + CI: true - name: Upload Playwright report on failure if: failure() @@ -110,4 +63,5 @@ jobs: with: name: playwright-report path: playwright-report/ - retention-days: 7 \ No newline at end of file + retention-days: 7 + diff --git a/.gitignore b/.gitignore index e96e5328de..6c7bfee487 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,11 @@ localhost.key privkey.pem terraform/.terraform/ +# Playwright +test-results/ +playwright-report/ +.playwright/ + storybook-static duplicates.json diff --git a/package.json b/package.json index 292e1c33e6..c7b42669d8 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "test": "NODE_ENV=test jest", "test:watch": "NODE_ENV=test jest --watch", "test:ci": "npm run lint && npm run test", + "e2e": "playwright test --headed", + "e2e:ci": "playwright test", "fetch-examples": "cross-env NODE_ENV=development node ./server/scripts/fetch-examples.js", "fetch-examples-gg": "cross-env NODE_ENV=development node ./server/scripts/fetch-examples-gg.js", "fetch-examples-ml5": "cross-env NODE_ENV=development node ./server/scripts/fetch-examples-ml5.js", diff --git a/playwright.config.ts b/playwright.config.ts index 130a0b1c98..af611b3272 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -6,9 +6,9 @@ export default defineConfig({ timeout: 120_000, use: { - baseURL: 'http://localhost:8000', - headless: true, + baseURL: 'http://localhost:8000' }, + projects: [ { name: 'chromium', diff --git a/tests/playwright/editor.spec.ts b/tests/playwright/editor.spec.ts index 440deed53d..cc1d7fda46 100644 --- a/tests/playwright/editor.spec.ts +++ b/tests/playwright/editor.spec.ts @@ -1,153 +1,105 @@ -import { test, expect, Page } from '@playwright/test'; - -async function dismissCookies(page: Page) { - try { - await page.waitForSelector('button', { timeout: 3_000 }); - await page.evaluate(() => { - const btn = Array.from(document.querySelectorAll('button')).find((b) => - /allow all|allow essential/i.test(b.textContent ?? '') - ) as HTMLElement | undefined; - btn?.click(); - }); - await page.waitForTimeout(400); - } catch { - /* no banner */ - } -} +import { test, expect } from '@playwright/test'; test.describe('p5.js Editor – Playwright E2E', () => { - test('editor loads and has a sketch iframe', async ({ page }) => { - await page.goto('http://localhost:8000', { - waitUntil: 'domcontentloaded', - timeout: 30_000 - }); - await dismissCookies(page); - await page.evaluate(() => { - (document.querySelector( - '[aria-label="Play sketch"]' - ) as HTMLElement)?.click(); - }); - const iframeHandle = await page.waitForSelector('iframe', { - timeout: 15_000 - }); - expect(iframeHandle).toBeTruthy(); - }); + test.beforeEach(async ({ page }) => { + await page.goto('/'); - test('can access iframe content frame', async ({ page }) => { - await page.goto('http://localhost:8000'); - await dismissCookies(page); - await page.waitForSelector('iframe'); - const body = page.frameLocator('iframe').first().locator('body'); - await expect(body).toBeAttached({ timeout: 10_000 }); - }); + // Wait for the page to be interactive before checking for the banner + await page.waitForSelector('.CodeMirror', { timeout: 30_000 }); - test('run button triggers sketch in iframe', async ({ page }) => { - await page.goto('http://localhost:8000'); - await dismissCookies(page); + // Dismiss cookie banner via JS — handles the case where the button + // is outside the viewport due to the Redux DevTools sidebar await page.evaluate(() => { - (document.querySelector( - '[aria-label="Play sketch"]' - ) as HTMLElement)?.click(); + const btn = Array.from(document.querySelectorAll('button')).find((b) => + /allow essential|allow all/i.test(b.textContent ?? '') + ) as HTMLElement | undefined; + btn?.click(); }); - await page.waitForSelector('iframe', { timeout: 10_000 }); - const iframeSrc = await page.locator('iframe').getAttribute('src'); - console.log('iframe src:', iframeSrc); - expect(iframeSrc).toBeTruthy(); - await expect(page.locator('iframe')).toBeVisible({ timeout: 10_000 }); - }); - test.skip('sketch execution via postMessage', async () => { - // FINDING: postMessage interception via page.evaluate() returns empty. - // The sketch iframe (localhost:8002) sends messages via window.parent.parent - // but these do not surface in Playwright's main page context. - // Testing sketch output would require CDP or a dedicated message relay - // — a candidate for GSoC implementation. + await page.waitForTimeout(400); }); - test('sketch console.log appears in editor console', async ({ page }) => { - await page.goto('http://localhost:8000'); - - await dismissCookies(page); - - await page.waitForFunction(() => { - const wrapper = document.querySelector('.CodeMirror') as any; - return !!wrapper?.CodeMirror; - }); - - await page.evaluate( - (newCode) => { - const cm = (document.querySelector('.CodeMirror') as any)?.CodeMirror; - - if (!cm) { - throw new Error('CodeMirror not found'); - } - - cm.setValue(newCode); - cm.refresh(); - - const root = document.querySelector('#root') as any; - - const fiberKey = Object.keys(root).find((k) => - k.startsWith('__reactContainer') - ); - - let node = root[fiberKey]; - let store: any = null; - - while (node) { - if (node.memoizedProps?.store) { - store = node.memoizedProps.store; - break; - } - - node = node.child; - } - - if (!store) { - throw new Error('Redux store not found'); - } - - const selectedFile = store - .getState() - .files.find((f: any) => f.isSelectedFile); + test('can execute code from the editor by clicking the Play button', async ({ + page + }) => { + const newCode = [ + 'function setup() {', + ' createCanvas(400, 400);', + '}', + '', + 'function draw() {', + ' background(220);', + " console.log('hi from sketch');", + ' noLoop();', + '}' + ].join('\n'); + + // Wait for CodeMirror to be ready + await page.waitForFunction( + () => { + const wrapper = document.querySelector('.CodeMirror') as any; + return (wrapper?.CodeMirror?.getValue?.() ?? '').length > 0; + }, + { timeout: 30_000 } + ); - if (!selectedFile) { - throw new Error('Selected file not found'); + // Update code via CodeMirror API + Redux dispatch + // (confirmed working approach from earlier diagnostic work) + await page.evaluate((code) => { + const cm = (document.querySelector('.CodeMirror') as any).CodeMirror; + cm.setValue(code); + cm.refresh(); + + const root = document.querySelector('#root') as any; + const fiberKey = Object.keys(root).find((k) => + k.startsWith('__reactContainer') + ); + let node = root[fiberKey]; + let store: any = null; + while (node) { + if (node.memoizedProps?.store) { + store = node.memoizedProps.store; + break; } + node = node.child; + } + if (!store) throw new Error('Redux store not found'); - store.dispatch({ - type: 'UPDATE_FILE_CONTENT', - id: selectedFile.id, - content: newCode - }); - }, - ` -function setup() { - createCanvas(400, 400); -} + const selectedFile = store + .getState() + .files.find((f: any) => f.isSelectedFile); + if (!selectedFile) throw new Error('No selected file'); -function draw() { - background(220); - console.log('hi from sketch'); - noLoop(); -} -` - ); + store.dispatch({ + type: 'UPDATE_FILE_CONTENT', + id: selectedFile.id, + content: code + }); + }, newCode); - await page.waitForTimeout(1000); + await page.waitForTimeout(500); + // Click Play await page.locator('#play-sketch').click(); - const openConsoleButton = page.locator('[aria-label="Open console"]'); + // Wait for the sketch iframe to confirm the sketch actually started + await page.waitForFunction( + () => + Array.from(document.querySelectorAll('iframe')).some((f) => + (f as HTMLIFrameElement).src.includes('8002') + ), + { timeout: 10_000 } + ); - if (await openConsoleButton.isVisible()) { - await openConsoleButton.click(); + // Open console if collapsed + const openConsole = page.getByLabel('Open console'); + if (await openConsole.isVisible().catch(() => false)) { + await openConsole.click(); } - await expect - .poll(() => page.locator('.preview-console__messages').textContent(), { - timeout: 15000 - }) - .toContain('hi from sketch'); + // Assert console output + await expect( + page.locator('.preview-console__messages') + ).toContainText('hi from sketch', { timeout: 15_000 }); }); }); From 3667d1d16ac1a376d1c9c4bc7396f0cb2131baa9 Mon Sep 17 00:00:00 2001 From: Geethegreat <86944224+Geethegreat@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:47:14 +0530 Subject: [PATCH 9/9] remove open console action --- tests/playwright/editor.spec.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/playwright/editor.spec.ts b/tests/playwright/editor.spec.ts index cc1d7fda46..559283da2c 100644 --- a/tests/playwright/editor.spec.ts +++ b/tests/playwright/editor.spec.ts @@ -91,12 +91,6 @@ test.describe('p5.js Editor – Playwright E2E', () => { { timeout: 10_000 } ); - // Open console if collapsed - const openConsole = page.getByLabel('Open console'); - if (await openConsole.isVisible().catch(() => false)) { - await openConsole.click(); - } - // Assert console output await expect( page.locator('.preview-console__messages')