From e88debaa21ae69fc3f016870ca26940f3511e862 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:13:26 +0000 Subject: [PATCH 1/4] Initial plan From d20777adca974b62719dacd7a18cd9fdf11f44e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:19:04 +0000 Subject: [PATCH 2/4] Add Playwright testing infrastructure with example tests Co-authored-by: lukasmatta <4323927+lukasmatta@users.noreply.github.com> --- .gitignore | 5 + docs/testing/playwright-approach.md | 321 +++++++++++++++++++++++ package-lock.json | 60 +++++ package.json | 5 + playwright.config.ts | 41 +++ playwright/components/cps-button.spec.ts | 61 +++++ playwright/components/cps-chip.spec.ts | 56 ++++ 7 files changed, 549 insertions(+) create mode 100644 docs/testing/playwright-approach.md create mode 100644 playwright.config.ts create mode 100644 playwright/components/cps-button.spec.ts create mode 100644 playwright/components/cps-chip.spec.ts diff --git a/.gitignore b/.gitignore index 4ca0f808..9d7cf88d 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,8 @@ cypress/screenshots/* # System files .DS_Store Thumbs.db + +# Playwright +/playwright-report/ +/playwright/.cache/ +/test-results/ diff --git a/docs/testing/playwright-approach.md b/docs/testing/playwright-approach.md new file mode 100644 index 00000000..4a70472f --- /dev/null +++ b/docs/testing/playwright-approach.md @@ -0,0 +1,321 @@ +# Playwright Testing Approach + +## Overview + +This document outlines the approach for implementing Playwright tests for the CPS Shared UI component library. Playwright has been chosen as a complementary testing tool to provide robust end-to-end (E2E) and integration testing capabilities alongside our existing Jest unit tests and Cypress E2E tests. + +## Runner Choice & Rationale + +### Why Playwright? + +We selected Playwright for the following reasons: + +1. **Modern Architecture**: Playwright provides a modern, actively maintained testing framework with excellent TypeScript support. + +2. **Multi-Browser Support**: Native support for Chromium, Firefox, and WebKit, ensuring cross-browser compatibility. + +3. **Reliability**: Playwright's auto-waiting and retry mechanisms reduce flakiness in tests. + +4. **Performance**: Tests run in parallel by default, providing faster test execution. + +5. **Developer Experience**: + - Excellent debugging tools including UI mode and trace viewer + - Built-in test generation and codegen tools + - Rich assertion library with async/await support + +6. **Complementary to Existing Tools**: + - **Jest**: Continues to handle unit tests for isolated component logic + - **Cypress**: Remains for full E2E workflow testing + - **Playwright**: Focuses on component-level integration and UI interaction testing + - **pa11y-ci**: Continues to handle accessibility testing + +## Component Testing Approach + +### Testing Strategy + +Playwright tests in this project focus on: + +1. **Component Integration**: Testing how components render and behave in the actual documentation/composition application +2. **User Interactions**: Validating click handlers, input changes, and other user interactions +3. **Visual Verification**: Ensuring components are visible and properly styled +4. **Accessibility**: Complementing pa11y-ci with interactive accessibility testing + +### Component Mounting Approach + +Rather than using component testing (which would require experimental packages), we leverage the existing **composition application** as a test harness: + +- Tests navigate to actual component pages (e.g., `/button`, `/chip`) +- Components are tested in their real rendering context +- This approach: + - Requires no additional mounting infrastructure + - Tests components as users see them + - Validates the documentation app alongside components + - Simplifies setup and maintenance + +## Directory Structure + +``` +cps-shared-ui/ +├── playwright/ # Playwright test directory +│ ├── components/ # Component-level tests +│ │ ├── cps-button.spec.ts # Button component tests +│ │ ├── cps-chip.spec.ts # Chip component tests +│ │ └── ... # Additional component tests +│ └── integration/ # Integration tests (future) +│ └── ... +├── playwright.config.ts # Playwright configuration +├── docs/ +│ └── testing/ +│ └── playwright-approach.md # This document +└── package.json # Updated with Playwright scripts +``` + +### File Naming Conventions + +- Component tests: `cps-{component-name}.spec.ts` +- Integration tests: `{feature-name}.spec.ts` +- Use `.spec.ts` extension (consistent with Jest) + +## Example Tests + +### Button Component Test + +```typescript +import { test, expect } from '@playwright/test'; + +test.describe('CPS Button Component', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/button'); + }); + + test('should display button with label', async ({ page }) => { + const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + await expect(button).toBeVisible(); + }); + + test('should be clickable and emit click event', async ({ page }) => { + const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + await button.locator('button').click(); + await expect(button.locator('button')).toBeEnabled(); + }); +}); +``` + +### Chip Component Test + +```typescript +import { test, expect } from '@playwright/test'; + +test.describe('CPS Chip Component', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/chip'); + }); + + test('should display chip with label', async ({ page }) => { + const chip = page.locator('cps-chip').first(); + await expect(chip).toBeVisible(); + }); + + test('should display closable chip with close button', async ({ page }) => { + const closableChip = page.locator('cps-chip') + .filter({ has: page.locator('.cps-chip-close-icon') }) + .first(); + await expect(closableChip).toBeVisible(); + }); +}); +``` + +## Test Patterns & Best Practices + +### Locator Strategies + +1. **Semantic Selectors**: Use component selectors (`cps-button`, `cps-chip`) +2. **Text-based**: Filter by visible text for better readability +3. **Data Attributes**: Use `data-testid` when semantic selectors aren't sufficient +4. **Class Selectors**: For internal component structure (use sparingly) + +### Async/Await Pattern + +All Playwright actions are asynchronous: +```typescript +await page.goto('/button'); +await button.click(); +await expect(element).toBeVisible(); +``` + +### Test Organization + +- Group related tests with `test.describe()` +- Use `test.beforeEach()` for common setup +- Keep tests focused on single behaviors +- Aim for descriptive test names + +## Visual Testing Plan + +### Current Approach + +Visual testing is handled through: +1. Playwright's built-in screenshot capabilities (on-demand) +2. Manual visual review during development +3. Existing accessibility checks via pa11y-ci + +### Future Enhancements + +Potential visual testing improvements: + +1. **Snapshot Testing**: Add visual regression testing using Playwright's screenshot comparison +2. **Percy Integration**: Consider Percy or similar visual testing platforms +3. **Responsive Testing**: Add viewport variations for mobile/tablet/desktop +4. **Theme Testing**: Validate components across different color schemes + +Implementation recommendations: +```typescript +// Example: Visual regression test (future) +test('button should match visual snapshot', async ({ page }) => { + await page.goto('/button'); + await expect(page).toHaveScreenshot('button-page.png'); +}); +``` + +## Running Tests + +### Local Development + +```bash +# Run all Playwright tests +npm run playwright + +# Run tests in UI mode (interactive) +npm run playwright:ui + +# Run tests in headed mode (see browser) +npm run playwright:headed + +# View test report +npm run playwright:report +``` + +### Debugging + +```bash +# Run in UI mode for debugging +npm run playwright:ui + +# Run specific test file +npx playwright test playwright/components/cps-button.spec.ts + +# Run with debug mode +PWDEBUG=1 npx playwright test +``` + +## CI Integration Approach + +### GitHub Actions Workflow + +Add a new job to `.github/workflows/cps-shared-ui-checkers.yml`: + +```yaml +playwright: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps chromium + + - name: Run Playwright tests + run: npm run playwright + + - name: Upload Playwright Report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 +``` + +### CI Configuration + +The `playwright.config.ts` already includes CI-specific settings: + +- **Retries**: Tests retry 2 times on CI (0 locally) +- **Workers**: Single worker on CI for stability +- **Web Server**: Auto-starts dev server before tests +- **Fail on `.only`**: Prevents accidentally committed focused tests + +### Test Artifacts + +On CI, Playwright automatically generates: +- HTML test report +- Screenshots on failure +- Test traces for debugging + +These are uploaded as GitHub Actions artifacts for review. + +## Migration Strategy + +### Phase 1: PoC (Current) +- ✅ Install and configure Playwright +- ✅ Create 2 example tests (button, chip) +- ✅ Document approach +- ✅ Verify local test execution + +### Phase 2: Expand Coverage +- Add tests for 5-10 most critical components +- Establish test patterns and conventions +- Add CI integration +- Train team on Playwright usage + +### Phase 3: Full Integration +- Comprehensive component coverage +- Visual regression testing +- Performance benchmarks +- Integration with existing testing workflows + +## Maintenance & Best Practices + +### When to Use Each Testing Tool + +| Tool | Use Case | +|------|----------| +| **Jest** | Unit tests, isolated component logic, service testing | +| **Playwright** | Component integration, UI interactions, visual verification | +| **Cypress** | Full E2E workflows, multi-page user journeys | +| **pa11y-ci** | Automated accessibility compliance checking | + +### Code Review Checklist + +- [ ] Tests use semantic locators +- [ ] Test names clearly describe behavior +- [ ] No hardcoded waits (use Playwright's auto-waiting) +- [ ] Tests are independent (no shared state) +- [ ] Appropriate use of `.first()`, `.nth()` for multiple elements + +### Performance Considerations + +- Run tests in parallel (default) +- Use `fullyParallel: true` for faster execution +- Limit unnecessary page navigations +- Reuse test context when possible + +## Resources + +- [Playwright Documentation](https://playwright.dev) +- [Playwright Best Practices](https://playwright.dev/docs/best-practices) +- [Playwright Test Generator](https://playwright.dev/docs/codegen) +- [Angular Testing Guide](https://angular.dev/guide/testing) + +## Conclusion + +Playwright provides a robust, modern testing solution that complements our existing test infrastructure. By leveraging the composition application as a test harness, we can efficiently test component behavior without complex setup. This approach balances comprehensive testing with maintainability and developer experience. diff --git a/package-lock.json b/package-lock.json index 0e037360..44dbd909 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@angular-devkit/build-angular": "^20.3.9", "@angular/cli": "~20.3.9", "@angular/compiler-cli": "^20.3.10", + "@playwright/test": "^1.58.0", "@types/express": "^4.17.25", "@types/jest": "^29.5.14", "@types/node": "^22.10.10", @@ -5492,6 +5493,21 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@playwright/test": { + "version": "1.58.0", + "integrity": "sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@primeuix/styled": { "version": "0.7.4", "integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==", @@ -17894,6 +17910,50 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.58.0", + "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.0", + "integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/portscanner": { "version": "2.2.0", "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", diff --git a/package.json b/package.json index e5fd519a..f583f4c2 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "test:a11y:local": "npm run start & sleep 10 && npm run test:a11y:summary && kill %1", "cypress:open": "cypress open", "cypress:run": "cypress run", + "playwright": "playwright test", + "playwright:ui": "playwright test --ui", + "playwright:headed": "playwright test --headed", + "playwright:report": "playwright show-report", "format": "prettier --write \"**/*.{ts,html}\"" }, "private": true, @@ -45,6 +49,7 @@ "@angular-devkit/build-angular": "^20.3.9", "@angular/cli": "~20.3.9", "@angular/compiler-cli": "^20.3.10", + "@playwright/test": "^1.58.0", "@types/express": "^4.17.25", "@types/jest": "^29.5.14", "@types/node": "^22.10.10", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..ca6e7ec1 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,41 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './playwright', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:4200', + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run start', + url: 'http://localhost:4200', + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, +}); diff --git a/playwright/components/cps-button.spec.ts b/playwright/components/cps-button.spec.ts new file mode 100644 index 00000000..bce5cc5c --- /dev/null +++ b/playwright/components/cps-button.spec.ts @@ -0,0 +1,61 @@ +import { test, expect } from '@playwright/test'; + +test.describe('CPS Button Component', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/button'); + }); + + test('should display button with label', async ({ page }) => { + // Find a button by its label text + const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + + await expect(button).toBeVisible(); + await expect(button.locator('.cps-button__text')).toBeVisible(); + }); + + test('should be clickable and emit click event', async ({ page }) => { + // Find a clickable button + const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + const buttonElement = button.locator('button'); + + await expect(buttonElement).toBeEnabled(); + await buttonElement.click(); + + // Verify the button is still enabled after click + await expect(buttonElement).toBeEnabled(); + }); + + test('should display disabled button', async ({ page }) => { + // Find disabled buttons on the page + const disabledButton = page.locator('cps-button button[disabled]').first(); + + await expect(disabledButton).toBeDisabled(); + }); + + test('should display button with icon', async ({ page }) => { + // Find a button with an icon + const buttonWithIcon = page.locator('cps-button').filter({ has: page.locator('cps-icon') }).first(); + + await expect(buttonWithIcon).toBeVisible(); + await expect(buttonWithIcon.locator('cps-icon')).toBeVisible(); + }); + + test('should apply different button types (solid, outlined, borderless)', async ({ page }) => { + // Check for solid buttons (default) + const solidButton = page.locator('cps-button .cps-button--solid').first(); + await expect(solidButton).toBeVisible(); + + // Check that button has proper classes + const buttonClasses = await solidButton.getAttribute('class'); + expect(buttonClasses).toContain('cps-button'); + }); + + test('should display different button sizes', async ({ page }) => { + // Check for buttons with size classes + const normalButton = page.locator('cps-button .cps-button--normal').first(); + + if (await normalButton.count() > 0) { + await expect(normalButton).toBeVisible(); + } + }); +}); diff --git a/playwright/components/cps-chip.spec.ts b/playwright/components/cps-chip.spec.ts new file mode 100644 index 00000000..58bc83c6 --- /dev/null +++ b/playwright/components/cps-chip.spec.ts @@ -0,0 +1,56 @@ +import { test, expect } from '@playwright/test'; + +test.describe('CPS Chip Component', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/chip'); + }); + + test('should display chip with label', async ({ page }) => { + // Find a chip by its label + const chip = page.locator('cps-chip').first(); + + await expect(chip).toBeVisible(); + await expect(chip.locator('.cps-chip-label')).toBeVisible(); + }); + + test('should display closable chip with close button', async ({ page }) => { + // Find closable chip with close icon + const closableChip = page.locator('cps-chip').filter({ has: page.locator('.cps-chip-close-icon') }).first(); + + if (await closableChip.count() > 0) { + await expect(closableChip).toBeVisible(); + await expect(closableChip.locator('.cps-chip-close-icon')).toBeVisible(); + } + }); + + test('should close chip when clicking close button', async ({ page }) => { + // Find a closable chip + const closableChip = page.locator('cps-chip').filter({ has: page.locator('.cps-chip-close-icon') }).first(); + + if (await closableChip.count() > 0) { + const closeButton = closableChip.locator('.cps-chip-close-icon'); + await closeButton.click(); + + // After clicking close, the chip might be removed or hidden + // This depends on the implementation + } + }); + + test('should display chip with icon', async ({ page }) => { + // Find chips with icons + const chipWithIcon = page.locator('cps-chip').filter({ has: page.locator('cps-icon') }).first(); + + if (await chipWithIcon.count() > 0) { + await expect(chipWithIcon).toBeVisible(); + await expect(chipWithIcon.locator('cps-icon')).toBeVisible(); + } + }); + + test('should display multiple chips', async ({ page }) => { + // Count chips on the page + const chips = page.locator('cps-chip'); + const chipCount = await chips.count(); + + expect(chipCount).toBeGreaterThan(0); + }); +}); From 45b1c7c0dcd59f523410309fa7a7cbea0b3f5f6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:22:14 +0000 Subject: [PATCH 3/4] Update tests to match actual page content and add documentation Co-authored-by: lukasmatta <4323927+lukasmatta@users.noreply.github.com> --- README.md | 9 ++++ playwright.config.ts | 2 +- playwright/README.md | 63 ++++++++++++++++++++++++ playwright/components/cps-button.spec.ts | 20 +++++--- 4 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 playwright/README.md diff --git a/README.md b/README.md index 35324d25..97f3aad7 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,15 @@ Execute `npm run generate-json-api` to generate documentation for any changes in `npm run test` +#### Run Playwright tests + +Playwright tests provide component-level integration and UI interaction testing. + +`npm run playwright` (headless run) or you can open Playwright UI mode using `npm run playwright:ui` + +For more details, see the [Playwright testing approach documentation](docs/testing/playwright-approach.md). + + #### Run Cypress e2e tests `npm run cypress:run` (headless run) or you can open Cypress tests using `npm run cypress:open` diff --git a/playwright.config.ts b/playwright.config.ts index ca6e7ec1..2c1c32ef 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -35,7 +35,7 @@ export default defineConfig({ webServer: { command: 'npm run start', url: 'http://localhost:4200', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, timeout: 120000, }, }); diff --git a/playwright/README.md b/playwright/README.md new file mode 100644 index 00000000..8b38791b --- /dev/null +++ b/playwright/README.md @@ -0,0 +1,63 @@ +# Playwright Tests + +This directory contains Playwright tests for the CPS Shared UI component library. + +## Structure + +``` +playwright/ +├── components/ # Component-level tests +│ ├── cps-button.spec.ts +│ ├── cps-chip.spec.ts +│ └── ... +└── integration/ # Integration tests (future) +``` + +## Running Tests + +```bash +# Run all tests +npm run playwright + +# Run tests in UI mode (interactive debugging) +npm run playwright:ui + +# Run tests in headed mode (see browser) +npm run playwright:headed + +# View test report +npm run playwright:report + +# Run specific test file +npx playwright test playwright/components/cps-button.spec.ts + +# Debug tests +PWDEBUG=1 npx playwright test +``` + +## Writing Tests + +Tests should follow these patterns: + +1. **Use semantic selectors**: Target components by their selectors (e.g., `cps-button`) +2. **Filter by visible text**: Use `.filter({ hasText: 'text' })` for readability +3. **Keep tests focused**: Each test should verify a single behavior +4. **Use descriptive names**: Test names should clearly describe what is being tested + +Example: + +```typescript +test('should display button with label', async ({ page }) => { + await page.goto('/button'); + const button = page.locator('cps-button').filter({ hasText: 'Normal button' }).first(); + await expect(button).toBeVisible(); +}); +``` + +## Test Coverage + +Currently covered components: +- ✅ Button +- ✅ Chip + +See `/docs/testing/playwright-approach.md` for comprehensive documentation. diff --git a/playwright/components/cps-button.spec.ts b/playwright/components/cps-button.spec.ts index bce5cc5c..8964b194 100644 --- a/playwright/components/cps-button.spec.ts +++ b/playwright/components/cps-button.spec.ts @@ -7,7 +7,7 @@ test.describe('CPS Button Component', () => { test('should display button with label', async ({ page }) => { // Find a button by its label text - const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + const button = page.locator('cps-button').filter({ hasText: 'Normal button' }).first(); await expect(button).toBeVisible(); await expect(button.locator('.cps-button__text')).toBeVisible(); @@ -15,7 +15,7 @@ test.describe('CPS Button Component', () => { test('should be clickable and emit click event', async ({ page }) => { // Find a clickable button - const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + const button = page.locator('cps-button').filter({ hasText: 'Normal button' }).first(); const buttonElement = button.locator('button'); await expect(buttonElement).toBeEnabled(); @@ -52,10 +52,18 @@ test.describe('CPS Button Component', () => { test('should display different button sizes', async ({ page }) => { // Check for buttons with size classes - const normalButton = page.locator('cps-button .cps-button--normal').first(); + const largeButton = page.locator('cps-button').filter({ hasText: 'Large button' }).first(); + const smallButton = page.locator('cps-button').filter({ hasText: 'Small button' }).first(); - if (await normalButton.count() > 0) { - await expect(normalButton).toBeVisible(); - } + await expect(largeButton).toBeVisible(); + await expect(smallButton).toBeVisible(); + }); + + test('should display loading state', async ({ page }) => { + // Find a button in loading state + const loadingButton = page.locator('cps-button').filter({ has: page.locator('cps-progress-circular') }).first(); + + await expect(loadingButton).toBeVisible(); + await expect(loadingButton.locator('cps-progress-circular')).toBeVisible(); }); }); From c7875c7f528752ac9b8ac822292013769a9fcc4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:25:31 +0000 Subject: [PATCH 4/4] Address code review feedback: improve test reliability and consistency Co-authored-by: lukasmatta <4323927+lukasmatta@users.noreply.github.com> --- docs/testing/playwright-approach.md | 4 +-- playwright.config.ts | 2 +- playwright/components/cps-button.spec.ts | 18 ++++++++++---- playwright/components/cps-chip.spec.ts | 31 ++++++------------------ 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/docs/testing/playwright-approach.md b/docs/testing/playwright-approach.md index 4a70472f..34e33b1a 100644 --- a/docs/testing/playwright-approach.md +++ b/docs/testing/playwright-approach.md @@ -89,12 +89,12 @@ test.describe('CPS Button Component', () => { }); test('should display button with label', async ({ page }) => { - const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + const button = page.locator('cps-button').filter({ hasText: 'Normal button' }).first(); await expect(button).toBeVisible(); }); test('should be clickable and emit click event', async ({ page }) => { - const button = page.locator('cps-button').filter({ hasText: 'Primary' }).first(); + const button = page.locator('cps-button').filter({ hasText: 'Normal button' }).first(); await button.locator('button').click(); await expect(button.locator('button')).toBeEnabled(); }); diff --git a/playwright.config.ts b/playwright.config.ts index 2c1c32ef..f595952c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -36,6 +36,6 @@ export default defineConfig({ command: 'npm run start', url: 'http://localhost:4200', reuseExistingServer: true, - timeout: 120000, + timeout: 60000, }, }); diff --git a/playwright/components/cps-button.spec.ts b/playwright/components/cps-button.spec.ts index 8964b194..b2a8b70a 100644 --- a/playwright/components/cps-button.spec.ts +++ b/playwright/components/cps-button.spec.ts @@ -40,7 +40,7 @@ test.describe('CPS Button Component', () => { await expect(buttonWithIcon.locator('cps-icon')).toBeVisible(); }); - test('should apply different button types (solid, outlined, borderless)', async ({ page }) => { + test('should apply solid button type', async ({ page }) => { // Check for solid buttons (default) const solidButton = page.locator('cps-button .cps-button--solid').first(); await expect(solidButton).toBeVisible(); @@ -48,15 +48,23 @@ test.describe('CPS Button Component', () => { // Check that button has proper classes const buttonClasses = await solidButton.getAttribute('class'); expect(buttonClasses).toContain('cps-button'); + expect(buttonClasses).toContain('cps-button--solid'); }); test('should display different button sizes', async ({ page }) => { - // Check for buttons with size classes - const largeButton = page.locator('cps-button').filter({ hasText: 'Large button' }).first(); - const smallButton = page.locator('cps-button').filter({ hasText: 'Small button' }).first(); - + // Check for buttons with large size + const largeButton = page.locator('cps-button .cps-button--large').first(); await expect(largeButton).toBeVisible(); + + const largeClasses = await largeButton.getAttribute('class'); + expect(largeClasses).toContain('cps-button--large'); + + // Check for buttons with small size + const smallButton = page.locator('cps-button .cps-button--small').first(); await expect(smallButton).toBeVisible(); + + const smallClasses = await smallButton.getAttribute('class'); + expect(smallClasses).toContain('cps-button--small'); }); test('should display loading state', async ({ page }) => { diff --git a/playwright/components/cps-chip.spec.ts b/playwright/components/cps-chip.spec.ts index 58bc83c6..eed317f7 100644 --- a/playwright/components/cps-chip.spec.ts +++ b/playwright/components/cps-chip.spec.ts @@ -15,35 +15,20 @@ test.describe('CPS Chip Component', () => { test('should display closable chip with close button', async ({ page }) => { // Find closable chip with close icon - const closableChip = page.locator('cps-chip').filter({ has: page.locator('.cps-chip-close-icon') }).first(); + const closableChips = page.locator('cps-chip').filter({ has: page.locator('.cps-chip-close-icon') }); - if (await closableChip.count() > 0) { - await expect(closableChip).toBeVisible(); - await expect(closableChip.locator('.cps-chip-close-icon')).toBeVisible(); - } - }); - - test('should close chip when clicking close button', async ({ page }) => { - // Find a closable chip - const closableChip = page.locator('cps-chip').filter({ has: page.locator('.cps-chip-close-icon') }).first(); - - if (await closableChip.count() > 0) { - const closeButton = closableChip.locator('.cps-chip-close-icon'); - await closeButton.click(); - - // After clicking close, the chip might be removed or hidden - // This depends on the implementation - } + // Verify at least one closable chip exists + await expect(closableChips.first()).toBeVisible(); + await expect(closableChips.first().locator('.cps-chip-close-icon')).toBeVisible(); }); test('should display chip with icon', async ({ page }) => { // Find chips with icons - const chipWithIcon = page.locator('cps-chip').filter({ has: page.locator('cps-icon') }).first(); + const chipsWithIcon = page.locator('cps-chip').filter({ has: page.locator('cps-icon') }); - if (await chipWithIcon.count() > 0) { - await expect(chipWithIcon).toBeVisible(); - await expect(chipWithIcon.locator('cps-icon')).toBeVisible(); - } + // Verify at least one chip with icon exists + await expect(chipsWithIcon.first()).toBeVisible(); + await expect(chipsWithIcon.first().locator('cps-icon')).toBeVisible(); }); test('should display multiple chips', async ({ page }) => {