Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions docs/src/api/class-locatorassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,9 @@ Snapshot name. Must have a `.png` or `.webp` extension, the screenshot is captur
### option: LocatorAssertions.toHaveScreenshot#1.timeout = %%-js-assertions-timeout-%%
* since: v1.23

### option: LocatorAssertions.toHaveScreenshot#1.signal = %%-js-assertions-signal-%%
* since: v1.62

### option: LocatorAssertions.toHaveScreenshot#1.animations = %%-screenshot-option-animations-default-disabled-%%
* since: v1.23

Expand Down Expand Up @@ -2059,6 +2062,9 @@ Note that screenshot assertions only work with Playwright test runner.
### option: LocatorAssertions.toHaveScreenshot#2.timeout = %%-js-assertions-timeout-%%
* since: v1.23

### option: LocatorAssertions.toHaveScreenshot#2.signal = %%-js-assertions-signal-%%
* since: v1.62

### option: LocatorAssertions.toHaveScreenshot#2.animations = %%-screenshot-option-animations-default-disabled-%%
* since: v1.23

Expand Down
6 changes: 6 additions & 0 deletions docs/src/api/class-pageassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ Snapshot name. Must have a `.png` or `.webp` extension, the screenshot is captur
### option: PageAssertions.toHaveScreenshot#1.timeout = %%-js-assertions-timeout-%%
* since: v1.23

### option: PageAssertions.toHaveScreenshot#1.signal = %%-js-assertions-signal-%%
* since: v1.62

### option: PageAssertions.toHaveScreenshot#1.animations = %%-screenshot-option-animations-default-disabled-%%
* since: v1.23

Expand Down Expand Up @@ -314,6 +317,9 @@ Note that screenshot assertions only work with Playwright test runner.
### option: PageAssertions.toHaveScreenshot#2.timeout = %%-js-assertions-timeout-%%
* since: v1.23

### option: PageAssertions.toHaveScreenshot#2.signal = %%-js-assertions-signal-%%
* since: v1.62

### option: PageAssertions.toHaveScreenshot#2.animations = %%-screenshot-option-animations-default-disabled-%%
* since: v1.23

Expand Down
10 changes: 7 additions & 3 deletions packages/playwright-core/src/client/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import fs from 'fs';
import * as inspector from 'inspector';
import path from 'path';

import { assertionAbortedMessage } from '@isomorphic/abortSignal';
import { assert } from '@isomorphic/assert';
import { headersObjectToArray } from '@isomorphic/headers';
import { trimStringWithEllipsis } from '@isomorphic/stringUtils';
Expand All @@ -32,7 +33,7 @@ import { Coverage } from './coverage';
import { DisposableObject, DisposableStub } from './disposable';
import { Download } from './download';
import { ElementHandle, determineScreenshotType } from './elementHandle';
import { PlaywrightError, TargetClosedError, isTargetClosedError, parseError, serializeError } from './errors';
import { AbortError, PlaywrightError, TargetClosedError, isTargetClosedError, parseError, serializeError } from './errors';
import { Events } from './events';
import { FileChooser } from './fileChooser';
import { Frame, verifyLoadState } from './frame';
Expand Down Expand Up @@ -77,6 +78,7 @@ export type ExpectScreenshotOptions = Omit<channels.PageExpectScreenshotOptions,
expected?: Buffer,
locator?: api.Locator,
timeout: number,
signal?: AbortSignal,
isNot: boolean,
mask?: api.Locator[],
};
Expand Down Expand Up @@ -622,7 +624,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
}

async _expectScreenshot(options: ExpectScreenshotOptions): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[], timedOut?: boolean}> {
const { timeout, ...optionsWithoutTimeout } = options;
const { timeout, signal, ...optionsWithoutTimeout } = options;
const mask = options?.mask ? options?.mask.map(locator => ({
frame: (locator as Locator)._frame._channel,
selector: (locator as Locator)._selector,
Expand All @@ -637,9 +639,11 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
isNot: !!options.isNot,
locator,
mask,
}, { signal: undefined, timeout });
}, { timeout, signal });
return { actual: result.actual };
} catch (e) {
if (e instanceof AbortError)
return { errorMessage: 'Error: ' + assertionAbortedMessage(e.cause) };
if (!(e instanceof PlaywrightError))
throw e;
const details = e.details as channels.PageExpectScreenshotErrorDetails;
Expand Down
3 changes: 3 additions & 0 deletions packages/playwright/src/matchers/toMatchSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type ToHaveScreenshotOptions = ToHaveScreenshotConfigOptions & {
mask?: Array<Locator>;
maskColor?: string;
omitBackground?: boolean;
signal?: AbortSignal;
};

// Keep in sync with above (begin).
Expand All @@ -64,6 +65,7 @@ const NonConfigProperties: (keyof ToHaveScreenshotOptions)[] = [
'mask',
'maskColor',
'omitBackground',
'signal',
];
// Keep in sync with above (end).

Expand Down Expand Up @@ -362,6 +364,7 @@ export async function toHaveScreenshot(
style,
isNot: !!this.isNot,
timeout,
signal: helper.options.signal,
type: screenshotType,
comparator: helper.options.comparator,
maxDiffPixels: helper.options.maxDiffPixels,
Expand Down
21 changes: 21 additions & 0 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9618,6 +9618,13 @@ interface LocatorAssertions {
*/
scale?: "css"|"device";

/**
* An optional [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that can cancel the
* assertion. Aborting the signal fails the assertion like a timeout: if the signal is aborted while the assertion is
* retrying, or is already aborted before the assertion starts, the assertion fails without retrying further.
*/
signal?: AbortSignal;

/**
* File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic
* elements, make elements invisible or change their properties to help you creating repeatable screenshots. This
Expand Down Expand Up @@ -9714,6 +9721,13 @@ interface LocatorAssertions {
*/
scale?: "css"|"device";

/**
* An optional [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that can cancel the
* assertion. Aborting the signal fails the assertion like a timeout: if the signal is aborted while the assertion is
* retrying, or is already aborted before the assertion starts, the assertion fails without retrying further.
*/
signal?: AbortSignal;

/**
* File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic
* elements, make elements invisible or change their properties to help you creating repeatable screenshots. This
Expand Down Expand Up @@ -10605,6 +10619,13 @@ export interface PageAssertionsToHaveScreenshotOptions {
*/
scale?: "css"|"device";

/**
* An optional [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that can cancel the
* assertion. Aborting the signal fails the assertion like a timeout: if the signal is aborted while the assertion is
* retrying, or is already aborted before the assertion starts, the assertion fails without retrying further.
*/
signal?: AbortSignal;

/**
* File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic
* elements, make elements invisible or change their properties to help you creating repeatable screenshots. This
Expand Down
37 changes: 37 additions & 0 deletions tests/playwright-test/to-have-screenshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,43 @@ test('should fail to screenshot a page with infinite animation', async ({ runInl
expect(fs.existsSync(testInfo.outputPath('a.spec.js-snapshots', 'is-a-test-1.png'))).toBe(false);
});

test('should fail like a timeout when aborted', async ({ runInlineTest }) => {
const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html'));
const result = await runInlineTest({
...playwrightConfig({}),
'a.spec.js': `
const { test, expect } = require('@playwright/test');
test('is a test', async ({ page }) => {
await page.goto('${infiniteAnimationURL}');
const controller = new AbortController();
const promise = expect(page).toHaveScreenshot({ animations: 'allow', timeout: 5000, signal: controller.signal });
await page.waitForTimeout(500);
controller.abort(new Error('stop it'));
await promise;
});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`operation was aborted: stop it`);
expect(result.output).not.toContain(`Timeout 5000ms exceeded`);
});

test('should fail when already aborted', async ({ runInlineTest }) => {
const result = await runInlineTest({
...playwrightConfig({}),
'a.spec.js': `
const { test, expect } = require('@playwright/test');
test('is a test', async ({ page }) => {
const controller = new AbortController();
controller.abort(new Error('already aborted'));
await expect(page).toHaveScreenshot({ signal: controller.signal });
});
`
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain(`Error: The assertion was aborted: already aborted`);
});

test('should disable animations by default', async ({ runInlineTest }, testInfo) => {
const cssTransitionURL = pathToFileURL(path.join(__dirname, '../assets/css-transition.html'));
const result = await runInlineTest({
Expand Down