Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
aa1952f
added component-error-filter.ts
alionazherdetska Nov 6, 2025
81b27e0
refactored component.cy
alionazherdetska Nov 6, 2025
710ae7e
small refatcors
alionazherdetska Nov 6, 2025
e01ff4a
added the missing components to angular integration packages
alionazherdetska Nov 6, 2025
7ceb237
small adjustemnts
alionazherdetska Nov 6, 2025
c14f405
small fixed for component-error-filter
alionazherdetska Nov 6, 2025
6f380af
improved the code
alionazherdetska Nov 6, 2025
e6bcffd
reset some of the files
alionazherdetska Nov 6, 2025
dbda7d7
Merge branch 'main' into e2e-tabs-component-angular-nextjs
alionazherdetska Nov 6, 2025
ad9fd2f
chore(components-angular): add missing components markup to Angular i…
alionazherdetska Nov 6, 2025
83f0acc
added the header to the app
alionazherdetska Nov 6, 2025
107c5eb
slight changes
alionazherdetska Nov 6, 2025
d0a0502
slight changes
alionazherdetska Nov 6, 2025
72ce540
reverted redundant changes
alionazherdetska Nov 7, 2025
372f242
reverted redundant changes
alionazherdetska Nov 7, 2025
50b557f
removed Logo since it is present in the header
alionazherdetska Nov 7, 2025
9c10643
avoided repetition
alionazherdetska Nov 7, 2025
825f59d
small test fix
alionazherdetska Nov 7, 2025
c160588
removed redundant link from LinArea
alionazherdetska Nov 7, 2025
ddf40de
added the temporary workaround
alionazherdetska Nov 7, 2025
7981ccf
Merge branch 'e2e-tabs-component-angular-nextjs' into add-missing-com…
alionazherdetska Nov 7, 2025
b81692f
temp fix
alionazherdetska Nov 7, 2025
d506756
temp fix
alionazherdetska Nov 7, 2025
421cb80
fix linting
alionazherdetska Nov 7, 2025
2f48bcc
Merge branch 'e2e-tabs-component-angular-nextjs' into add-missing-com…
alionazherdetska Nov 7, 2025
4dac3aa
removed duplications
alionazherdetska Nov 7, 2025
e7a1fae
added comments
alionazherdetska Nov 7, 2025
1d84773
removed duplications
alionazherdetska Nov 7, 2025
8afc90e
reverted redundant changes
alionazherdetska Nov 10, 2025
93c538a
reverted redundant changes
alionazherdetska Nov 10, 2025
557b571
reset the files
alionazherdetska Nov 10, 2025
2b3126d
reset the files
alionazherdetska Nov 10, 2025
b76c598
reset redundant changes
alionazherdetska Nov 10, 2025
0c8b361
fixed the typos
alionazherdetska Nov 10, 2025
1630c40
added the files
alionazherdetska Nov 10, 2025
d4c482a
Merge branch 'main' into e2e-tabs-component-angular-nextjs
alionazherdetska Nov 10, 2025
5b0830a
reset the file
alionazherdetska Nov 10, 2025
0ef7f61
Merge branch 'e2e-tabs-component-angular-nextjs' of https://github.co…
alionazherdetska Nov 10, 2025
cdd3229
reset redundant changes
alionazherdetska Nov 10, 2025
14d92e5
reverted the changes
alionazherdetska Nov 10, 2025
7881907
temp fix
alionazherdetska Nov 10, 2025
b2e5e3e
fixed the error.tamplate
alionazherdetska Nov 10, 2025
f37a61f
added missing import
alionazherdetska Nov 10, 2025
750629c
removed redundant import
alionazherdetska Nov 10, 2025
81db496
small fixes
alionazherdetska Nov 10, 2025
19d6873
fixed the naming
alionazherdetska Nov 10, 2025
845234f
fixed console.error.filter
alionazherdetska Nov 10, 2025
6a0e2dd
small fixes
alionazherdetska Nov 10, 2025
36bb2a1
small fixes
alionazherdetska Nov 10, 2025
e3fbb8f
small changes
alionazherdetska Nov 10, 2025
241e499
syncronized the error-filter files
alionazherdetska Nov 10, 2025
092f29a
Merge branch 'main' into e2e-tabs-component-angular-nextjs
oliverschuerch Nov 14, 2025
6126c9c
fixed sonar
alionazherdetska Nov 14, 2025
8ad3a49
Merge branch 'e2e-tabs-component-angular-nextjs' of https://github.co…
alionazherdetska Nov 14, 2025
eb3c295
Update packages/nextjs-integration/playwright/tests/ssr.spec.ts
alionazherdetska Nov 18, 2025
cd573a5
Update packages/nextjs-integration/playwright/tests/csr.spec.ts
alionazherdetska Nov 18, 2025
5333e03
refactored `component.cy.ts`
alionazherdetska Nov 18, 2025
5875647
Merge branch 'e2e-tabs-component-angular-nextjs' of https://github.co…
alionazherdetska Nov 18, 2025
6119051
Merge branch 'main' into e2e-tabs-component-angular-nextjs
alionazherdetska Nov 18, 2025
d96d02f
small fixes for the markup in ANgular package
alionazherdetska Nov 18, 2025
70696ce
small refactor
alionazherdetska Nov 18, 2025
5bff468
updated the header markup in nextjs witht the latest update
alionazherdetska Nov 18, 2025
19dff69
tiny refactors
alionazherdetska Nov 18, 2025
99e03b1
fix(component): fix TypeError for `post-stepper` component
alionazherdetska Nov 18, 2025
8f50312
Merge branch '6682-bug-typeerror-cannot-read-properties-of-undefined-…
alionazherdetska Nov 18, 2025
ed0f17d
small changes
alionazherdetska Nov 18, 2025
b0bde1c
fix(component): added a changeset
alionazherdetska Nov 18, 2025
7732596
removed the workaround
alionazherdetska Nov 18, 2025
a2c1785
refactored error-filters
alionazherdetska Nov 18, 2025
33f3be5
removed redundant code
alionazherdetska Nov 18, 2025
97e27fc
added fullyparallel locally
alionazherdetska Nov 18, 2025
3a99b44
increased the number of workers
alionazherdetska Nov 18, 2025
d4b109b
try
alionazherdetska Nov 18, 2025
a7aedd3
reverted redundant changes
alionazherdetska Nov 18, 2025
203f69a
removed redundant tests
alionazherdetska Nov 19, 2025
8a84399
removed redundant code
alionazherdetska Nov 19, 2025
5217279
possible fix
alionazherdetska Nov 19, 2025
a373435
added cypress e2e
alionazherdetska Nov 19, 2025
ac86fa7
temp fix
alionazherdetska Nov 19, 2025
aa8df87
fixed ssr file
alionazherdetska Nov 19, 2025
7afa86d
small refactors
alionazherdetska Nov 19, 2025
c56b532
reverted redundant code
alionazherdetska Nov 19, 2025
a9db460
improved the naming
alionazherdetska Nov 19, 2025
4a2fb77
improved the naming
alionazherdetska Nov 19, 2025
04213f2
improved the naming
alionazherdetska Nov 19, 2025
22cb7a7
Merge branch 'main' into e2e-tabs-component-angular-nextjs
alionazherdetska Nov 19, 2025
0f3a015
chnaged the comment
alionazherdetska Nov 19, 2025
48b3577
defined the stepItems in connectedCallback
alionazherdetska Nov 19, 2025
e81f23a
Merge branch '6682-bug-typeerror-cannot-read-properties-of-undefined-…
alionazherdetska Nov 19, 2025
03e7080
Merge branch 'main' into e2e-tabs-component-angular-nextjs
alionazherdetska Nov 19, 2025
0e86ec9
fixed the type
alionazherdetska Nov 19, 2025
8425f8f
Merge branch 'e2e-tabs-component-angular-nextjs' of https://github.co…
alionazherdetska Nov 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/e2e-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
- 'packages/documentation/**'
- 'packages/styles/**'
- 'packages/components/**'
- 'packages/components-angular/**'
- 'packages/internet-header/**'
- 'packages/icons/**'
- 'packages/tokens/tokensstudio-generated/tokens.json'
Expand Down
1 change: 1 addition & 0 deletions packages/components-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"postbuild": "pnpm copy:styles:dist",
"clean": "rimraf dist .angular",
"e2e": "ng e2e --watch=false",
"e2e:ci": "xvfb-run -a ng e2e --watch=false",
"e2e:watch": "ng e2e",
"lint": "ng lint",
"lint:fix": "ng lint --fix",
Expand Down
Copy link
Contributor Author

@alionazherdetska alionazherdetska Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the CI runs both components and components-angular suites in the same pipeline, test suite and test names are now explicit (prefixed with components-angular) to avoid ambiguous logs when both suites appear in CI.

Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import * as Components from '@swisspost/design-system-components/dist';
import { setupComponentErrorCapture, assertNoComponentErrors } from '../support/component-error-filter';
import { componentNames } from '@swisspost/design-system-components/dist/component-names.json';

const COMPONENT_TAG_NAMES = Object.keys(Components)
.filter(c => /^Post([A-Z][a-z]+)+$/.test(c))
.map(c => c.replace(/([a-z0–9])([A-Z])/g, '$1-$2').toLowerCase());

describe('Components', () => {
describe('components-angular (consumer-app)', () => {
beforeEach(() => {
cy.visit('/');
cy.window().then(win => {
cy.wrap(cy.spy(win.console, 'error')).as('consoleError');
});

it('components-angular: should contain all components', () => {
componentNames.forEach(componentName => {
cy.get(componentName).first().should('exist');
});
});

it('should not log any error', () => {
cy.get('@consoleError').should('not.have.been.called');
it('components-angular: all components should be hydrated', () => {
componentNames.forEach(componentName => {
cy.get(componentName).first().should('have.class', 'hydrated');
});
});

COMPONENT_TAG_NAMES.forEach(tagName => {
it(`should contain <${tagName}>`, () => {
cy.get(tagName).should('exist');
it('components-angular: should not have console errors', () => {
const errorCapture = setupComponentErrorCapture(componentNames as string[]);

cy.visit('/', {
onBeforeLoad: errorCapture.onBeforeLoad
});

assertNoComponentErrors(errorCapture.errors, componentNames as string[]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
type CapturedError = {
message: string;
source: 'console' | 'error';
stack?: string;
timestamp: number;
};

/**
* Sets up error monitoring for specific components.
* Captures console.error and uncaught errors.
*
* @param componentNames - Array of component names to monitor for errors
* @returns Object containing:
* - errors: Live array of error messages (used by tests)
* - onBeforeLoad: Function to set up error capture on window before app loads
*/
export function setupComponentErrorCapture(componentNames: string[]) {
const errors: string[] = []; // live array used by tests
const captured: CapturedError[] = [];

// Pre-compute lowercase names to avoid repeated lowercasing in the hot path
const lowerCaseComponentNames = componentNames.map(n => n.toLowerCase());

return {
errors,
captured,
onBeforeLoad
};

/**
* Sets up error listeners on the application window before it loads.
* Intercepts console.error calls and window error events.
*
* @param win - Cypress AUT window object
*/
function onBeforeLoad(win: Cypress.AUTWindow): void {
// Store original console.error to call it after capturing
const originalError = win.console.error.bind(win.console);

// Intercept console.error() calls from the application
cy.stub(win.console, 'error').callsFake((...args: any[]) => {
originalError(...args);
// Convert all arguments to strings and join them
const message = args.map(extractMessage).join(' ');
pushError(message, 'console');
});

// Capture uncaught exceptions and runtime errors from the page
win.addEventListener('error', (e: ErrorEvent) => {
const message = e.message || extractMessage(e.error);
const stack = e.error?.stack;
pushError(message, 'error', stack);
});
}

/**
* Adds an error to the captured errors array if it passes all filters.
* Applies relevance checking.
*
* @param message - The error message to capture
* @param source - The source of the error ('console' or 'error')
* @param stack - Optional stack trace for the error
*/
function pushError(message: string, source: CapturedError['source'], stack?: string): void {
// Skip errors unrelated to our components
if (!isRelevant(message)) return;

const entry: CapturedError = { message, source, stack, timestamp: Date.now() };
captured.push(entry);
errors.push(message);
}

/**
* Extracts a readable message from various error formats.
* Handles strings, Error objects, and other values gracefully.
*
* @param arg - Error object, string, or any value to extract message from
* @returns Extracted error message as a string
*/
function extractMessage(arg: unknown): string {
if (typeof arg === 'string') return arg;

// Handle Error-like objects
if (arg && typeof arg === 'object') {
if ('message' in arg && arg.message) {
return String(arg.message);
}
if ('stack' in arg && arg.stack) {
return String(arg.stack);
}
}

// Fallback: try to serialize or convert to string
try {
return JSON.stringify(arg);
} catch {
return String(arg);
}
}

/**
* Determines if an error message is relevant to the monitored components.
* An error is considered relevant if it mentions any of the component names.
*
* @param message - Error message to check for component relevance
* @returns True if the error mentions a monitored component, false otherwise
*/
function isRelevant(message: string): boolean {
const lower = message.toLowerCase();
return lowerCaseComponentNames.some(n => lower.includes(n));
}
}

/**
* Asserts that no component errors were captured during testing.
* Throws an error with detailed information if any errors were found.
*
* @param errors - Array of error messages captured during test execution
* @param componentNames - Array of component names that were monitored
* @throws Error if any errors were captured, with formatted error details
*/
export function assertNoComponentErrors(errors: string[], componentNames: string[]): void {
cy.then(() => {
if (errors.length === 0) return;

const list = errors.map((m, i) => `${i + 1}. ${m}`).join('\n');
throw new Error(`Found ${errors.length} error(s) for [${componentNames.join(', ')}]:\n${list}`);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ <h3>{{ section.title }}</h3>

<div class="container">
<post-breadcrumbs
home-url="/"
home-text="Home"
homeUrl="/"
homeText="Home"
label="Breadcrumbs"
menu-label="More breadcrumb items"
menuLabel="More breadcrumb items"
>
<post-breadcrumb-item url="/section1">Section 1</post-breadcrumb-item>
<post-breadcrumb-item url="/section2">Section 2</post-breadcrumb-item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ <h2>Post Menu</h2>
<post-menu-trigger for="menu-one">
<button class="btn btn-primary">Menu button</button>
</post-menu-trigger>
<post-menu id="menu-one">
<post-menu id="menu-one" label="Menu">
<post-menu-item>
<button>Example 1</button>
</post-menu-item>
Expand All @@ -90,7 +90,7 @@ <h2>Post Popover</h2>
class="palette palette-accent"
id="popover-one"
placement="top"
close-button-caption="Close"
closeButtonCaption="Close"
><div id="popoverContent">
<h2 class="h6">Optional title</h2>
<p class="mb-0">
Expand All @@ -107,7 +107,7 @@ <h2>Post Popovercontainer</h2>

<div class="my-24">
<h2>Post Rating</h2>
<post-rating></post-rating>
<post-rating label="Rating"></post-rating>
</div>

<div class="my-24">
Expand Down
4 changes: 1 addition & 3 deletions packages/nextjs-integration/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ export default defineConfig({
testDir: './playwright/tests',
outputDir: './playwright/results',
snapshotDir: './playwright/snapshots',
/* 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 ? 1 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
workers: process.env.CI ? 2 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? 'github' : 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Page, ConsoleMessage } from '@playwright/test';

const IGNORE_ERROR_PATTERNS = [
/^hydration failed because/i,
/^server rendered html didn't match/i,
/^there was an error while hydrating/i,
/^hydration error/i,
/^a tree hydrated but/i,
];

type CapturedError = {
message: string;
source: 'console';
stack?: string;
timestamp: number;
};

/**
* Sets up error monitoring for specific components.
* Captures console.error messages.
* Excludes known hydration errors (SSR-specific).
*
* @param page - Playwright page object to monitor
* @param componentNames - Array of component names to monitor for errors
* @returns Object containing:
* - errors: Live array of error messages (used by tests)
* - captured: Array of detailed error objects with metadata
* - dispose: Function to clean up event listeners
*/
export function setupComponentErrorCapture(page: Page, componentNames: string[]) {
const errors: string[] = []; // live array used by tests
const captured: CapturedError[] = [];

// Pre-compute lowercase names to avoid repeated lowercasing in the hot path
const lowerCaseComponentNames = componentNames.map(n => n.toLowerCase());

page.on('console', onConsole);

return { errors, dispose, captured } as const;

/**
* Event handler for console messages from the page.
* Captures console.error messages and passes them to pushError.
*
* @param msg - Playwright ConsoleMessage object
*/
function onConsole(msg: ConsoleMessage): void {
try {
if (msg.type() === 'error') {
pushError(msg.text(), 'console');
}
} catch {
// ignore handler errors to prevent test interference
}
}

/**
* Adds an error to the captured errors array if it passes all filters.
* Applies relevance checking and ignore pattern filtering.
*
* @param message - The error message to capture
* @param source - The source of the error ('console')
* @param stack - Optional stack trace for the error
*/
function pushError(message: string, source: CapturedError['source'], stack?: string): void {
// Skip hydration errors and errors unrelated to our components
if (isIgnoredError(message) || !isRelevant(message)) return;

const entry: CapturedError = { message, source, stack, timestamp: Date.now() };
captured.push(entry);
errors.push(message);
}

/**
* Checks if an error message matches known patterns that should be ignored.
* Currently filters out hydration-related errors and other non-critical messages.
*
* @param text - Error message to check against ignore patterns
* @returns True if the error should be ignored, false otherwise
*/
function isIgnoredError(text: string): boolean {
// Check if error message matches any known hydration/non-critical patterns
return IGNORE_ERROR_PATTERNS.some(pattern => pattern.test(text));
}

/**
* Determines if an error message is relevant to the monitored components.
* An error is considered relevant if it mentions any of the component names.
*
* @param message - Error message to check for component relevance
* @returns True if the error mentions a monitored component, false otherwise
*/
function isRelevant(message: string): boolean {
const lower = message.toLowerCase();
return lowerCaseComponentNames.some(n => lower.includes(n));
}

/**
* Removes event listeners and cleans up resources.
* Should be called after tests complete to prevent memory leaks.
* Safe to call multiple times.
*/
function dispose(): void {
try {
page.off('console', onConsole);
} catch {
// best-effort teardown - don't throw during cleanup
}
}
}

/**
* Asserts that no component errors were captured during testing.
* Throws an error with detailed information if any errors were found.
*
* @param errors - Array of error messages captured during test execution
* @param componentNames - Array of component names that were monitored
* @throws Error if any errors were captured, with formatted error details
*/
export function assertNoComponentErrors(errors: string[], componentNames: string[]): void {
if (errors.length === 0) return;

const list = errors.map((m, i) => `${i + 1}. ${m}`).join('\n');
throw new Error(`Found ${errors.length} error(s) for [${componentNames.join(', ')}]:\n${list}`);
}
6 changes: 3 additions & 3 deletions packages/nextjs-integration/playwright/tests/csr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ test.describe('CSR compatibility', () => {
await page.goto('/csr');
});

test('should contain every component tag at least once', async ({ page }) => {
test('all components should be hydrated', async ({ page }) => {
for (const componentName of componentNames) {
const component = page.locator(componentName).first();
await expect(component).toHaveCount(1);
const component = page.locator(`${componentName}[data-hydrated]`).first();
await expect(component).toBeAttached();
}
});

Expand Down
Loading
Loading