Skip to content

Commit 60fd41f

Browse files
test(e2e): add Playwright setup and plan card e2e tests
- configure Playwright to use the existing aem up dev server (port 6456) - add fixture page for nx-campaign-plan-card with importmap for da-lit - 7 tests covering render, collapse/expand, run button state, task status, and nx-plan-run event dispatch - add e2e script to package.json - add run-e2e job to CI workflow - allow devDependencies in playwright.config.js and e2e specs via ESLint override Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent 0aec447 commit 60fd41f

7 files changed

Lines changed: 258 additions & 1 deletion

File tree

.github/workflows/lint_test.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,24 @@ jobs:
3131

3232
- name: Run the tests
3333
run: npm t
34+
35+
run-e2e:
36+
name: Running e2e tests
37+
runs-on: ubuntu-latest
38+
steps:
39+
- name: Checkout repository
40+
uses: actions/checkout@v4
41+
42+
- name: Set up Node.js
43+
uses: actions/setup-node@v4
44+
with:
45+
node-version: 20.x
46+
47+
- name: Install dependencies
48+
run: npm ci
49+
50+
- name: Install Playwright browsers
51+
run: npx playwright install --with-deps chromium
52+
53+
- name: Run e2e tests
54+
run: npm run e2e

eslint.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,12 @@ export default defineConfig([
8585
'no-unused-expressions': 0,
8686
},
8787
},
88+
{
89+
// Allow devDependencies in Playwright config and e2e specs
90+
files: ['playwright.config.js', 'nx2/test/e2e/**/*.js'],
91+
rules: {
92+
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
93+
},
94+
},
8895
]);
8996

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Plan Card Fixture</title>
6+
<script type="importmap">
7+
{
8+
"imports": {
9+
"da-lit": "/nx2/deps/lit/dist/index.js"
10+
}
11+
}
12+
</script>
13+
</head>
14+
<body>
15+
<nx-campaign-plan-card id="card"></nx-campaign-plan-card>
16+
<script type="module">
17+
import '/nx2/blocks/chat/messages/campaign-plan-card.js';
18+
const card = document.getElementById('card');
19+
card.plan = {
20+
title: 'Test Plan',
21+
description: 'A test description',
22+
tasks: [
23+
{ id: '1', label: 'Read content', status: 'pending' },
24+
{ id: '2', label: 'Write draft', status: 'pending' },
25+
],
26+
};
27+
</script>
28+
</body>
29+
</html>

nx2/test/e2e/plan-card.spec.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { test, expect } from '@playwright/test';
2+
3+
const FIXTURE = '/nx2/test/e2e/fixtures/plan-card.html';
4+
5+
// Pierce shadow DOM: Playwright auto-pierces with >> css= syntax
6+
const shadow = (host, selector) => host.locator(`css=${selector}`);
7+
8+
test.describe('nx-campaign-plan-card', () => {
9+
test.beforeEach(async ({ page }) => {
10+
await page.goto(FIXTURE);
11+
// Wait for Lit to render the shadow root
12+
await page.waitForSelector('nx-campaign-plan-card');
13+
await page.waitForFunction(() => {
14+
const el = document.getElementById('card');
15+
return el?.shadowRoot?.querySelector('.plan-title') != null;
16+
});
17+
});
18+
19+
test('renders title and description', async ({ page }) => {
20+
const card = page.locator('nx-campaign-plan-card');
21+
await expect(shadow(card, '.plan-title')).toHaveText('Test Plan');
22+
await expect(shadow(card, '.plan-description')).toHaveText('A test description');
23+
});
24+
25+
test('renders task list with correct count', async ({ page }) => {
26+
const card = page.locator('nx-campaign-plan-card');
27+
await expect(shadow(card, 'nx-task-item')).toHaveCount(2);
28+
});
29+
30+
test('collapses and expands on chevron click', async ({ page }) => {
31+
const card = page.locator('nx-campaign-plan-card');
32+
const tasks = shadow(card, '.plan-tasks');
33+
34+
// Starts expanded
35+
await expect(tasks).toBeVisible();
36+
37+
// Collapse
38+
await shadow(card, '.plan-icon-btn').click();
39+
await expect(tasks).not.toBeVisible();
40+
41+
// Expand again
42+
await shadow(card, '.plan-icon-btn').click();
43+
await expect(tasks).toBeVisible();
44+
});
45+
46+
test('run button is enabled initially and dispatches nx-plan-run', async ({ page }) => {
47+
const card = page.locator('nx-campaign-plan-card');
48+
const runBtn = shadow(card, '.plan-btn-run');
49+
50+
await expect(runBtn).toBeEnabled();
51+
await expect(runBtn).toHaveText('Run');
52+
53+
const eventFired = page.evaluate(() => new Promise((resolve) => {
54+
document.getElementById('card').addEventListener('nx-plan-run', () => resolve(true), { once: true });
55+
}));
56+
await shadow(card, '.plan-btn-run').click();
57+
await expect(await eventFired).toBe(true);
58+
});
59+
60+
test('shows Running... and disables run button when a task is running', async ({ page }) => {
61+
await page.evaluate(() => {
62+
const card = document.getElementById('card');
63+
card.plan = {
64+
title: 'Test Plan',
65+
description: 'A test description',
66+
tasks: [
67+
{ id: '1', label: 'Read content', status: 'running' },
68+
{ id: '2', label: 'Write draft', status: 'pending' },
69+
],
70+
};
71+
});
72+
73+
const card = page.locator('nx-campaign-plan-card');
74+
const runBtn = shadow(card, '.plan-btn-run');
75+
await expect(runBtn).toHaveText('Running...');
76+
await expect(runBtn).toBeDisabled();
77+
});
78+
79+
test('reflects task status on nx-task-item elements', async ({ page }) => {
80+
await page.evaluate(() => {
81+
const card = document.getElementById('card');
82+
card.plan = {
83+
title: 'Test Plan',
84+
description: 'A test description',
85+
tasks: [
86+
{ id: '1', label: 'Read content', status: 'done' },
87+
{ id: '2', label: 'Write draft', status: 'running' },
88+
],
89+
};
90+
});
91+
92+
const card = page.locator('nx-campaign-plan-card');
93+
const items = shadow(card, 'nx-task-item');
94+
await expect(items.nth(0)).toHaveAttribute('status', 'done');
95+
await expect(items.nth(1)).toHaveAttribute('status', 'running');
96+
});
97+
98+
test('shows Done and disables run button when all tasks complete', async ({ page }) => {
99+
await page.evaluate(() => {
100+
const card = document.getElementById('card');
101+
card.plan = {
102+
title: 'Test Plan',
103+
description: 'A test description',
104+
tasks: [
105+
{ id: '1', label: 'Read content', status: 'done' },
106+
{ id: '2', label: 'Write draft', status: 'done' },
107+
],
108+
};
109+
});
110+
111+
const card = page.locator('nx-campaign-plan-card');
112+
const runBtn = shadow(card, '.plan-btn-run');
113+
await expect(runBtn).toHaveText('Done');
114+
await expect(runBtn).toBeDisabled();
115+
});
116+
});

package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"nx2:test:file:watch": "wtr --config ./nx2/test/wtr.config.mjs --node-resolve --port=2000 --coverage --watch",
3535
"nx2:build:da-lit": "esbuild --format=esm --minify ./nx2/deps/lit/src/index.js --bundle --outfile=./nx2/deps/lit/dist/index.js",
3636
"nx2:build:spectrum": "node nx2/deps/spectrum/build.js",
37-
"nx2:build:mdast": "esbuild --format=esm --minify ./nx2/deps/mdast/src/index.js --bundle --outfile=./nx2/deps/mdast/dist/index.js"
37+
"nx2:build:mdast": "esbuild --format=esm --minify ./nx2/deps/mdast/src/index.js --bundle --outfile=./nx2/deps/mdast/dist/index.js",
38+
"e2e": "playwright test"
3839
},
3940
"repository": {
4041
"type": "git",
@@ -50,6 +51,7 @@
5051
"@adobe/eslint-config-helix": "^3.0.17",
5152
"@adobe/spectrum-tokens": "^14.2.2",
5253
"@esm-bundle/chai": "4.3.4-fix.0",
54+
"@playwright/test": "^1.61.0",
5355
"@web/dev-server-import-maps": "^0.2.0",
5456
"@web/test-runner": "^0.18.0",
5557
"@web/test-runner-commands": "0.9.0",

playwright.config.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: 'nx2/test/e2e',
5+
timeout: 10000,
6+
use: {
7+
baseURL: 'http://localhost:6456',
8+
},
9+
projects: [
10+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
11+
],
12+
webServer: {
13+
command: 'aem up --port=6456',
14+
url: 'http://localhost:6456',
15+
reuseExistingServer: !process.env.CI,
16+
timeout: 30000,
17+
},
18+
});

0 commit comments

Comments
 (0)