From 931d23192587eac0565b1a33da48910c5f52d589 Mon Sep 17 00:00:00 2001 From: Rainer Hahnekamp <rainer.hahnekamp@gmail.com> Date: Sun, 5 Jan 2025 17:42:27 +0100 Subject: [PATCH 1/2] test(devtools): add e2e tests for devtools --- apps/demo/e2e/devtools.spec.ts | 62 +++++++++++++++++++ apps/demo/eslint.config.cjs | 10 ++- apps/demo/playwright.config.ts | 35 +++++++++++ apps/demo/project.json | 28 ++++----- .../src/app/devtools/todo-detail.component.ts | 23 ++++--- package.json | 2 +- pnpm-lock.yaml | 14 +++-- 7 files changed, 138 insertions(+), 36 deletions(-) create mode 100644 apps/demo/e2e/devtools.spec.ts create mode 100644 apps/demo/playwright.config.ts diff --git a/apps/demo/e2e/devtools.spec.ts b/apps/demo/e2e/devtools.spec.ts new file mode 100644 index 0000000..0dc3cbe --- /dev/null +++ b/apps/demo/e2e/devtools.spec.ts @@ -0,0 +1,62 @@ +import { test, expect } from '@playwright/test'; +import { Action } from '@ngrx/store'; + +test('has title', async ({ page }) => { + await page.goto(''); + + await page.evaluate(() => { + window['devtoolsSpy'] = []; + + window['__REDUX_DEVTOOLS_EXTENSION__'] = { + connect: () => { + return { + send: (data: Action) => { + window['devtoolsSpy'].push(data); + }, + }; + }, + }; + }); + await page.getByRole('link', { name: 'DevTools' }).click(); + await page + .getByRole('row', { name: 'Go for a walk' }) + .getByRole('checkbox') + .click(); + await page + .getByRole('row', { name: 'Exercise' }) + .getByRole('checkbox') + .click(); + + await expect( + page.getByRole('region', { name: 'Go for a walk' }) + ).toBeVisible(); + await expect(page.getByRole('region', { name: 'Exercise' })).toBeVisible(); + + await page + .getByRole('row', { name: 'Go for a walk' }) + .getByRole('checkbox') + .click(); + await page + .getByRole('row', { name: 'Exercise' }) + .getByRole('checkbox') + .click(); + + await expect( + page.getByRole('region', { name: 'Go for a walk' }) + ).toBeHidden(); + await expect(page.getByRole('region', { name: 'Exercise' })).toBeHidden(); + + const devtoolsActions = await page.evaluate(() => window['devtoolsSpy']); + + expect(devtoolsActions).toEqual([ + { type: 'add todo' }, + { type: 'select todo 1' }, + { type: 'Store Update' }, + { type: 'select todo 4' }, + { type: 'Store Update' }, + { type: 'select todo 1' }, + { type: 'Store Update' }, + { type: 'select todo 4' }, + { type: 'Store Update' }, + ]); +}); diff --git a/apps/demo/eslint.config.cjs b/apps/demo/eslint.config.cjs index 8831209..0d9ee19 100644 --- a/apps/demo/eslint.config.cjs +++ b/apps/demo/eslint.config.cjs @@ -1,7 +1,10 @@ +const playwright = require('eslint-plugin-playwright'); const nx = require('@nx/eslint-plugin'); const baseConfig = require('../../eslint.config.cjs'); module.exports = [ + playwright.configs['flat/recommended'], + ...baseConfig, ...nx.configs['flat/angular'], ...nx.configs['flat/angular-template'], @@ -25,5 +28,10 @@ module.exports = [ }, ], }, - } + }, + { + files: ['**/*.ts', '**/*.js'], + // Override or add rules here + rules: {}, + }, ]; diff --git a/apps/demo/playwright.config.ts b/apps/demo/playwright.config.ts new file mode 100644 index 0000000..d1911d9 --- /dev/null +++ b/apps/demo/playwright.config.ts @@ -0,0 +1,35 @@ +import { defineConfig, devices } from '@playwright/test'; +import * as path from 'node:path'; + +// For CI, you may want to set BASE_URL to the deployed application. +const baseURL = process.env['BASE_URL'] || 'http://localhost:4200'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'pnpm start', + url: 'http://localhost:4200', + reuseExistingServer: true, + cwd: path.join(__dirname, '../..'), + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); diff --git a/apps/demo/project.json b/apps/demo/project.json index 2f19bc0..dc10fbb 100644 --- a/apps/demo/project.json +++ b/apps/demo/project.json @@ -8,21 +8,14 @@ "targets": { "build": { "executor": "@angular-devkit/build-angular:application", - "outputs": [ - "{options.outputPath}" - ], + "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/apps/demo", "index": "apps/demo/src/index.html", "browser": "apps/demo/src/main.ts", - "polyfills": [ - "zone.js" - ], + "polyfills": ["zone.js"], "tsConfig": "apps/demo/tsconfig.app.json", - "assets": [ - "apps/demo/src/favicon.ico", - "apps/demo/src/assets" - ], + "assets": ["apps/demo/src/favicon.ico", "apps/demo/src/assets"], "styles": [ "@angular/material/prebuilt-themes/deeppurple-amber.css", "apps/demo/src/styles.css" @@ -73,18 +66,21 @@ }, "lint": { "executor": "@nx/eslint:lint", - "outputs": [ - "{options.outputFile}" - ] + "outputs": ["{options.outputFile}"] }, "test": { "executor": "@nx/jest:jest", - "outputs": [ - "{workspaceRoot}/coverage/{projectRoot}" - ], + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { "jestConfig": "apps/demo/jest.config.ts" } + }, + "e2e": { + "executor": "@nx/playwright:playwright", + "outputs": ["{workspaceRoot}/dist/.playwright/apps/demo"], + "options": { + "config": "apps/demo/playwright.config.ts" + } } } } diff --git a/apps/demo/src/app/devtools/todo-detail.component.ts b/apps/demo/src/app/devtools/todo-detail.component.ts index 6d819b7..91ec342 100644 --- a/apps/demo/src/app/devtools/todo-detail.component.ts +++ b/apps/demo/src/app/devtools/todo-detail.component.ts @@ -23,12 +23,14 @@ const TodoDetailStore = signalStore( @Component({ selector: 'demo-todo-detail', - template: ` <mat-card> - <mat-card-title>{{ todo().name }}</mat-card-title> - <mat-card-content> - <textarea>{{ todo().description }}</textarea> - </mat-card-content> - </mat-card>`, + template: ` <section [attr.aria-label]="todo().name"> + <mat-card> + <mat-card-title>{{ todo().name }}</mat-card-title> + <mat-card-content> + <textarea>{{ todo().description }}</textarea> + </mat-card-content> + </mat-card> + </section>`, imports: [MatCardModule], providers: [TodoDetailStore], styles: ` @@ -42,11 +44,8 @@ export class TodoDetailComponent { todo = input.required<Todo>(); constructor() { - effect( - () => { - renameDevtoolsName(this.#todoDetailStore, `todo-${this.todo().id}`); - }, - { allowSignalWrites: true } - ); + effect(() => { + renameDevtoolsName(this.#todoDetailStore, `todo-${this.todo().id}`); + }); } } diff --git a/package.json b/package.json index 4de3b37..2f0ff86 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "autoprefixer": "^10.4.19", "eslint": "^9.8.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-playwright": "^0.15.3", + "eslint-plugin-playwright": "^1.6.2", "eslint-plugin-unused-imports": "^4.1.4", "husky": "^9.0.11", "jest": "^29.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ea275e..8da9140 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,8 +148,8 @@ importers: specifier: ^9.0.0 version: 9.1.0(eslint@9.17.0(jiti@1.21.6)) eslint-plugin-playwright: - specifier: ^0.15.3 - version: 0.15.3(eslint@9.17.0(jiti@1.21.6)) + specifier: ^1.6.2 + version: 1.8.3(eslint@9.17.0(jiti@1.21.6)) eslint-plugin-unused-imports: specifier: ^4.1.4 version: 4.1.4(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.17.0(jiti@1.21.6)) @@ -5178,10 +5178,11 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-plugin-playwright@0.15.3: - resolution: {integrity: sha512-LQMW5y0DLK5Fnpya7JR1oAYL2/7Y9wDiYw6VZqlKqcRGSgjbVKNqxraphk7ra1U3Bb5EK444xMgUlQPbMg2M1g==} + eslint-plugin-playwright@1.8.3: + resolution: {integrity: sha512-h87JPFHkz8a6oPhn8GRGGhSQoAJjx0AkOv1jME6NoMk2FpEsfvfJJNaQDxLSqSALkCr0IJXPGTnp6SIRVu5Nqg==} + engines: {node: '>=16.6.0'} peerDependencies: - eslint: '>=7' + eslint: '>=8.40.0' eslint-plugin-jest: '>=25' peerDependenciesMeta: eslint-plugin-jest: @@ -14593,9 +14594,10 @@ snapshots: dependencies: eslint: 9.17.0(jiti@1.21.6) - eslint-plugin-playwright@0.15.3(eslint@9.17.0(jiti@1.21.6)): + eslint-plugin-playwright@1.8.3(eslint@9.17.0(jiti@1.21.6)): dependencies: eslint: 9.17.0(jiti@1.21.6) + globals: 13.24.0 eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.17.0(jiti@1.21.6)): dependencies: From 93c9440577817f03a210e4a6902b004723c63cdb Mon Sep 17 00:00:00 2001 From: Rainer Hahnekamp <rainer.hahnekamp@gmail.com> Date: Sun, 5 Jan 2025 18:05:31 +0100 Subject: [PATCH 2/2] test(devtools): add e2e tests for devtools --- .github/workflows/build.yml | 1 + apps/demo/jest.config.ts | 2 +- package.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3facedb..450daa0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,7 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm run lint:all - run: pnpm run test:all + - run: pnpm run test:e2e - run: pnpm run build:all - run: ./integration-tests.sh diff --git a/apps/demo/jest.config.ts b/apps/demo/jest.config.ts index 8623305..94d9334 100644 --- a/apps/demo/jest.config.ts +++ b/apps/demo/jest.config.ts @@ -1,4 +1,3 @@ - export default { displayName: 'demo', preset: '../../jest.preset.js', @@ -13,6 +12,7 @@ export default { }, ], }, + testPathIgnorePatterns: ['e2e'], transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], snapshotSerializers: [ 'jest-preset-angular/build/serializers/no-ng-attributes', diff --git a/package.json b/package.json index 2f0ff86..be23442 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "start": "nx serve demo", "lint:all": "nx run-many --targets=lint", "test:all": "nx run-many --targets=test", + "test:e2e": "nx e2e demo", "build:all": "nx run-many --targets=build", "verify:all": "nx run-many --targets=lint,test,build" },