mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
test: Make Playwright notification waits assert instead of returning false
`NotificationsPage.waitForNotification`, `closeNotificationByText` and `waitForNotificationAndClose` caught their own timeouts and returned `false`. No caller ever read the boolean, so a missing toast silently no-op'd: tests carried on against unfinished state and failed later with a misleading error, and a toast that failed to close stayed over the top-right of the app (error toasts have `duration: 0`) where it intercepts subsequent clicks. They now return `Promise<void>` and throw. `closeNotificationByText` asserts the real invariant — the toast is no longer on screen — and tolerates the toast auto-dismissing before the close click, so it does not trade one flake for another. This surfaces specs whose toast strings no longer exist in `@n8n/i18n` and were therefore asserting nothing. Two are fixed here: - ADO-1338 waited for "Workflow successfully executed"; the real string is "Workflow executed successfully" - the expression-modal save keybind waited for "Saved successfully", removed along with the save toast — now asserts on the save request instead Known remaining fallout, not yet addressed: a number of other specs relied on the silent behaviour and need auditing before this can land. `capabilities/task-runner.spec.ts` "should execute Python with task runner enabled" is one — its only assertion is `getCanvasNodes()).toHaveCount(2)`, so it is green locally even though the run does not succeed without the container stack.
This commit is contained in:
parent
94dc5f5209
commit
fcf833de54
|
|
@ -54,63 +54,50 @@ export class NotificationsPage {
|
|||
|
||||
/**
|
||||
* Clicks the close button on the FIRST notification matching the text.
|
||||
* Fast execution with short timeouts for snappy notifications.
|
||||
* Throws if the notification is still on screen afterwards - a toast left
|
||||
* open covers the top-right of the app and intercepts later clicks.
|
||||
* @param text The text of the notification to close.
|
||||
* @param options Optional configuration
|
||||
*/
|
||||
async closeNotificationByText(
|
||||
text: string | RegExp,
|
||||
options: { timeout?: number } = {},
|
||||
): Promise<boolean> {
|
||||
): Promise<void> {
|
||||
const { timeout = 2000 } = options;
|
||||
const notification = this.getNotificationByTitle(text).first();
|
||||
|
||||
try {
|
||||
const notification = this.getNotificationByTitle(text).first();
|
||||
await notification.waitFor({ state: 'visible', timeout });
|
||||
|
||||
const closeBtn = notification.locator('.el-notification__closeBtn');
|
||||
await closeBtn.click({ timeout: 500 });
|
||||
|
||||
// Quick check that it's gone - don't wait long
|
||||
await notification.waitFor({ state: 'hidden', timeout: 1000 });
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
await notification.locator('.el-notification__closeBtn').click({ timeout });
|
||||
} catch {
|
||||
// It may have auto-dismissed; the hidden check below is the real contract.
|
||||
}
|
||||
|
||||
await notification.waitFor({ state: 'hidden', timeout });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a notification to appear with specific text.
|
||||
* Reasonable timeout for waiting, but still faster than before.
|
||||
* Wait for a notification to appear with specific text. Throws if it never shows -
|
||||
* callers use this as a synchronisation point, so a silent miss lets the test run
|
||||
* on against unfinished state and fail later with a misleading error.
|
||||
* @param text The text to search for in notification title.
|
||||
* @param options Optional configuration
|
||||
*/
|
||||
async waitForNotification(
|
||||
text: string | RegExp,
|
||||
options: { timeout?: number } = {},
|
||||
): Promise<boolean> {
|
||||
): Promise<void> {
|
||||
const { timeout = 5000 } = options;
|
||||
|
||||
try {
|
||||
const notification = this.getNotificationByTitle(text).first();
|
||||
await notification.waitFor({ state: 'visible', timeout });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
await this.getNotificationByTitle(text).first().waitFor({ state: 'visible', timeout });
|
||||
}
|
||||
|
||||
// Wait for notification and then close it
|
||||
async waitForNotificationAndClose(
|
||||
text: string | RegExp,
|
||||
options: { timeout?: number } = {},
|
||||
): Promise<boolean> {
|
||||
): Promise<void> {
|
||||
const { timeout = 3000 } = options;
|
||||
const isVisible = await this.waitForNotification(text, { timeout });
|
||||
if (!isVisible) {
|
||||
return false;
|
||||
}
|
||||
return await this.closeNotificationByText(text, { timeout });
|
||||
await this.waitForNotification(text, { timeout });
|
||||
await this.closeNotificationByText(text, { timeout });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ test.describe(
|
|||
}) => {
|
||||
await setupRequirements(requirements);
|
||||
await n8n.workflowComposer.executeWorkflowAndWaitForNotification(
|
||||
'Workflow successfully executed',
|
||||
'Workflow executed successfully',
|
||||
);
|
||||
|
||||
await n8n.canvas.openNode('Discourse1');
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ test.describe(
|
|||
await n8n.ndv.fillExpressionEditorModalInput('{{ "hello"');
|
||||
await expect(n8n.ndv.getExpressionEditorModalOutput()).toContainText('hello');
|
||||
|
||||
// There is no save toast any more, so assert on the save request itself
|
||||
const saved = n8n.canvas.waitForSaveWorkflowCompleted();
|
||||
await input.press('ControlOrMeta+s');
|
||||
await n8n.notifications.waitForNotificationAndClose('Saved successfully');
|
||||
await saved;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user