Skip to content

Commit 981e321

Browse files
JustasMonkevclaude
andauthored
feat(expect): support pseudo-element in toHaveCSS (#40092)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2e4822e commit 981e321

8 files changed

Lines changed: 37 additions & 6 deletions

File tree

docs/src/api/class-locatorassertions.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,12 @@ CSS property name.
17471747

17481748
CSS property value.
17491749

1750+
### option: LocatorAssertions.toHaveCSS.pseudo
1751+
* since: v1.60
1752+
- `pseudo` <[PseudoElement]<"before"|"after">>
1753+
1754+
Pseudo-element to read computed styles from.
1755+
17501756
### option: LocatorAssertions.toHaveCSS.timeout = %%-js-assertions-timeout-%%
17511757
* since: v1.18
17521758

packages/injected/src/injectedScript.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1612,7 +1612,7 @@ export class InjectedScript {
16121612
matches: new ExpectedTextMatcher(options.expectedText[0]).matchesClassList(this, element.classList, /* partial */ expression === 'to.contain.class'),
16131613
};
16141614
} else if (expression === 'to.have.css') {
1615-
received = this.window.getComputedStyle(element).getPropertyValue(options.expressionArg);
1615+
received = this.window.getComputedStyle(element, options.pseudo ? `::${options.pseudo}` : undefined).getPropertyValue(options.expressionArg);
16161616
} else if (expression === 'to.have.id') {
16171617
received = element.id;
16181618
} else if (expression === 'to.have.text') {

packages/playwright-core/src/protocol/validator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,7 @@ scheme.FrameExpectParams = tObject({
19991999
selector: tOptional(tString),
20002000
expression: tString,
20012001
expressionArg: tOptional(tAny),
2002+
pseudo: tOptional(tEnum(['before', 'after'])),
20022003
expectedText: tOptional(tArray(tType('ExpectedTextValue'))),
20032004
expectedNumber: tOptional(tFloat),
20042005
expectedValue: tOptional(tType('SerializedArgument')),

packages/playwright/src/matchers/matchers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,17 +318,17 @@ export function toHaveCount(
318318
}, expected, options);
319319
}
320320

321-
export function toHaveCSS(this: ExpectMatcherStateInternal, locator: LocatorEx, name: string, expected: string | RegExp, options?: { timeout?: number }): Promise<MatcherResult<any, any>>;
321+
export function toHaveCSS(this: ExpectMatcherStateInternal, locator: LocatorEx, name: string, expected: string | RegExp, options?: { timeout?: number, pseudo?: 'before' | 'after' }): Promise<MatcherResult<any, any>>;
322322
export function toHaveCSS(
323323
this: ExpectMatcherStateInternal,
324324
locator: LocatorEx,
325325
name: string,
326326
expected: string | RegExp,
327-
options?: { timeout?: number },
327+
options?: { timeout?: number, pseudo?: 'before' | 'after' },
328328
) {
329329
return toMatchText.call(this, 'toHaveCSS', locator, 'Locator', async (isNot, timeout) => {
330330
const expectedText = serializeExpectedTextValues([expected]);
331-
return await locator._expect('to.have.css', { expressionArg: name, expectedText, isNot, timeout });
331+
return await locator._expect('to.have.css', { expressionArg: name, expectedText, isNot, pseudo: options?.pseudo, timeout });
332332
}, expected, options);
333333
}
334334

packages/playwright/types/test.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9223,6 +9223,11 @@ interface LocatorAssertions {
92239223
* @param options
92249224
*/
92259225
toHaveCSS(name: string, value: string|RegExp, options?: {
9226+
/**
9227+
* Pseudo-element to read computed styles from.
9228+
*/
9229+
pseudo?: "before"|"after";
9230+
92269231
/**
92279232
* Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
92289233
*/

packages/protocol/src/channels.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3444,6 +3444,7 @@ export type FrameExpectParams = {
34443444
selector?: string,
34453445
expression: string,
34463446
expressionArg?: any,
3447+
pseudo?: 'before' | 'after',
34473448
expectedText?: ExpectedTextValue[],
34483449
expectedNumber?: number,
34493450
expectedValue?: SerializedArgument,
@@ -3454,6 +3455,7 @@ export type FrameExpectParams = {
34543455
export type FrameExpectOptions = {
34553456
selector?: string,
34563457
expressionArg?: any,
3458+
pseudo?: 'before' | 'after',
34573459
expectedText?: ExpectedTextValue[],
34583460
expectedNumber?: number,
34593461
expectedValue?: SerializedArgument,

packages/protocol/src/protocol.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,11 @@ Frame:
29332933
selector: string?
29342934
expression: string
29352935
expressionArg: json?
2936+
pseudo:
2937+
type: enum?
2938+
literals:
2939+
- before
2940+
- after
29362941
expectedText:
29372942
type: array?
29382943
items: ExpectedTextValue
@@ -4593,5 +4598,3 @@ JsonPipe:
45934598
closed:
45944599
parameters:
45954600
reason: string?
4596-
4597-

tests/page/expect-misc.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,20 @@ test.describe('toHaveCSS', () => {
522522
await expect(locator).toHaveCSS('color', 'rgb(255, 0, 0)');
523523
});
524524

525+
test('should support pseudo element', async ({ page }) => {
526+
await page.setContent(`
527+
<style>
528+
#node::before {
529+
color: rgb(255, 0, 0);
530+
content: "Text content";
531+
}
532+
</style>
533+
<div id=node></div>
534+
`);
535+
const locator = page.locator('#node');
536+
await expect(locator).toHaveCSS('color', 'rgb(255, 0, 0)', { pseudo: 'before' });
537+
});
538+
525539
test('custom css properties', async ({ page }) => {
526540
await page.setContent('<div id=node style="--custom-color-property:#FF00FF;">Text content</div>');
527541
const locator = page.locator('#node');

0 commit comments

Comments
 (0)