diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index ac5b53f141..d7588faed9 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -167,7 +167,7 @@ jobs: path: dist # Not injecting the token will exclude the brand packages, but this is fine for e2e tests. - run: npm ci --prefer-offline --no-audit --include=optional - - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + - run: npx playwright test --reporter=blob --trace=on --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: ${{ !cancelled() }} with: @@ -184,12 +184,14 @@ jobs: - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: lts/krypton + cache: 'npm' + - run: npm ci --prefer-offline --no-audit --include=optional - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: path: all-blob-reports pattern: blob-report-* merge-multiple: true - - run: npx playwright merge-reports --reporter html ./all-blob-reports + - run: npx playwright merge-reports --reporter html,blob ./all-blob-reports - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: html-report--attempt-${{ github.run_attempt }} diff --git a/playwright-merge.config.ts b/playwright-merge.config.ts deleted file mode 100644 index a68545654d..0000000000 --- a/playwright-merge.config.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) Siemens 2016 - 2026 - * SPDX-License-Identifier: MIT - */ -import { PlaywrightTestConfig } from '@playwright/test'; - -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config: PlaywrightTestConfig = { - testDir: './playwright/e2e/element', - snapshotDir: './playwright/snapshots', - outputDir: './playwright/results/tests', - reporter: [ - ['github'], - [ - 'html', - { - open: 'on-failure', - outputFolder: './playwright/results/preview' - } - ], - [ - 'junit', - { - outputFile: `./playwright/results/reports/report-e2e.xml`, - includeProjectInTestName: true - } - ], - [ - './playwright/reporters/playwright-axe-reporter.ts', - { - outputFile: './playwright/results/a11y/accessibility-report.json', - htmlOutputDir: './playwright/results/a11y/tests' - } - ] - ] -}; - -export default config; diff --git a/playwright.config.ts b/playwright.config.ts index b4272a2f26..fb88f0cbe2 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -127,8 +127,8 @@ const config: PlaywrightTestConfig = { /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: `http://${localAddress}:${port}`, - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: isCI ? 'on-first-retry' : 'retain-on-failure', + /* Retain a full trace from the failing attempt. See https://playwright.dev/docs/trace-viewer */ + trace: 'retain-on-failure', viewport: baseViewport, diff --git a/playwright/e2e/element-examples/si-filtered-search.spec.ts b/playwright/e2e/element-examples/si-filtered-search.spec.ts index 9f304cbb9f..558cce57bd 100644 --- a/playwright/e2e/element-examples/si-filtered-search.spec.ts +++ b/playwright/e2e/element-examples/si-filtered-search.spec.ts @@ -71,6 +71,10 @@ test.describe('filtered search', () => { await expect(page.getByRole('option', { name: 'Karlsruhe' })).toHaveClass(/active/); await page.keyboard.type('annover'); await expect(page.getByRole('option').first()).not.toBeVisible(); // Ensures that the view was updated by Angular after typing. + // Guard against dropped keystrokes under CPU load: confirm full value before committing. + await expect( + page.locator('.pill-group', { hasText: 'Location' }).getByRole('combobox') + ).toHaveValue('Hannover'); await page.keyboard.press('Enter'); await expect(freeTextSearch).toBeFocused(); await freeTextSearch.fill('Building:House'); diff --git a/projects/element-ng/filtered-search/si-filtered-search-value.component.ts b/projects/element-ng/filtered-search/si-filtered-search-value.component.ts index 65b3b339bc..281471b489 100644 --- a/projects/element-ng/filtered-search/si-filtered-search-value.component.ts +++ b/projects/element-ng/filtered-search/si-filtered-search-value.component.ts @@ -4,10 +4,13 @@ */ import { CdkMonitorFocus, FocusOrigin } from '@angular/cdk/a11y'; import { + afterNextRender, ChangeDetectionStrategy, Component, computed, ElementRef, + inject, + Injector, input, model, OnInit, @@ -48,6 +51,8 @@ import { SiFilteredSearchTypeaheadComponent } from './values/typeahead/si-filter changeDetection: ChangeDetectionStrategy.OnPush }) export class SiFilteredSearchValueComponent implements OnInit { + private readonly injector = inject(Injector); + readonly value = model.required(); readonly definition = input.required(); readonly disabled = input.required(); @@ -123,16 +128,19 @@ export class SiFilteredSearchValueComponent implements OnInit { this.active.set(true); this.hasPendingFocus = true; - setTimeout(() => { - if (field === 'value') { - this.valueInput()?.focus(); - } else if (field === 'operator') { - this.operatorInput()?.nativeElement.focus(); - } else { - (this.operatorInput()?.nativeElement ?? this.valueInput())?.focus(); - } - this.hasPendingFocus = false; - }); + afterNextRender( + () => { + if (field === 'value') { + this.valueInput()?.focus(); + } else if (field === 'operator') { + this.operatorInput()?.nativeElement.focus(); + } else { + (this.operatorInput()?.nativeElement ?? this.valueInput())?.focus(); + } + this.hasPendingFocus = false; + }, + { injector: this.injector } + ); } protected backspaceOverflow(): void {