Skip to content

Commit

Permalink
cherry-pick(#15670): Revert "feat(matchers): add toContainClass (#15491
Browse files Browse the repository at this point in the history
…)" (#15678)

This reverts commit e4debd0.
  • Loading branch information
mxschmitt committed Jul 15, 2022
1 parent eae7bde commit b2e3c8c
Show file tree
Hide file tree
Showing 6 changed files with 7 additions and 234 deletions.
95 changes: 1 addition & 94 deletions docs/src/api/class-locatorassertions.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,99 +721,6 @@ await Expect(locator).ToBeVisibleAsync();
### option: LocatorAssertions.toBeVisible.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.18

## async method: LocatorAssertions.toContainClass
* since: v1.24
* langs:
- alias-java: containsClass

Ensures the [Locator] points to an element that contains the given CSS class (or multiple).
In contrast to [`method: LocatorAssertions.toHaveClass`] which requires that the [Locator] has exactly the provided classes, `toContainClass` verifies that the [Locator] has a subset (or all) of the given CSS classes.

```html
<div class='foo bar baz' id='component'>
<div class='item alice'></div>
<div class='item bob'></div>
</div>
```

```js
const locator = page.locator('#component');
await expect(locator).toContainClass('bar baz'); // pass, both classes are on element
await expect(locator).toContainClass('ba'); // fail, element has no 'ba' class

const itemLocator = page.locator('#component .item');
await expect(itemLocator).toContainClass(['alice', 'bob']); // pass, first element has alice, second bob
await expect(itemLocator).toContainClass(['alice', 'bob carl']); // no carl class found on second item element
await expect(itemLocator).toContainClass(['alice', 'bob', 'foobar']); // we expect 3 elements with the item class, but there are only 2
```

```java
Locator locator = page.locator("#component");
assertThat(locator).containsClass("bar baz"); // pass, both classes are on element
assertThat(locator).containsClass("ba"); // fail, element has no 'ba' class

Locator itemLocator = page.locator("#component .item");
assertThat(itemLocator).toContainClass(new String[] {"alice", "bob"}); // pass, first element has alice, second bob
assertThat(itemLocator).toContainClass(new String[] {"alice", "bob carl"}); // no carl class found on second item element
assertThat(itemLocator).toContainClass(new String[] {"alice", "bob", "foobar"}); // we expect 3 elements with the item class, but there are only 2
```

```python async
from playwright.async_api import expect

locator = page.locator('#component')
expect(locator).to_contain_class('bar baz') # pass, both classes are on element
expect(locator).to_contain_class('ba') # fail, element has no 'ba' class

item_locator = page.locator('#component .item')
expect(item_locator).to_contain_class(['alice', 'bob']) # pass, first element has alice, second bob
expect(item_locator).to_contain_class(['alice', 'bob carl']) # no carl class found on second item element
expect(item_locator).to_contain_class(['alice', 'bob', 'foobar']) # we expect 3 elements with the item class, but there are only 2
```

```python sync
from playwright.sync_api import expect

locator = page.locator('#component')
await expect(locator).to_contain_class('bar baz') # pass, both classes are on element
await expect(locator).to_contain_class('ba') # fail, element has no 'ba' class

item_locator = page.locator('#component .item')
await expect(item_locator).to_contain_class(['alice', 'bob']) # pass, first element has alice, second bob
await expect(item_locator).to_contain_class(['alice', 'bob carl']) # no carl class found on second item element
await expect(item_locator).to_contain_class(['alice', 'bob', 'foobar']) # we expect 3 elements with the item class, but there are only 2
```

```csharp
var locator = Page.Locator("#component");
await Expect(locator).ToContainClassAsync("bar baz"); // pass, both classes are on element
await Expect(locator).ToContainClassAsync("ba"); // fail, element has no "ba" class
var itemLocator = page.locator("#component .item");
await Expect(itemLocator).ToContainClassAsync(new string[]{"alice", "bob"}); // pass, first element has alice, second bob
await Expect(itemLocator).ToContainClassAsync(new string[]{"alice", "bob carl"}); // no carl class found on second item element
await Expect(itemLocator).ToContainClassAsync(new string[]{"alice", "bob", "foobar"}); // we expect 3 elements with the item class, but there are only 2
```

Note that locator must point to a single element when passing a string or to multiple elements when passing an array.

### param: LocatorAssertions.toContainClass.expected
* since: v1.24
- `expected` <[string]|[Array]<[string]>>

Expected classnames, whitespace separated. When passing an array, the given classes must be present on the locator elements.

### option: LocatorAssertions.toContainClass.ignoreCase
* since: v1.24
- `ignoreCase` <[boolean]>

Whether to perform case-insensitive match.

### option: LocatorAssertions.toContainClass.timeout = %%-js-assertions-timeout-%%
* since: v1.24
### option: LocatorAssertions.toContainClass.timeout = %%-csharp-java-python-assertions-timeout-%%
* since: v1.24

## async method: LocatorAssertions.toContainText
* since: v1.20
* langs:
Expand Down Expand Up @@ -977,7 +884,7 @@ Expected attribute value.
- alias-java: hasClass

Ensures the [Locator] points to an element with given CSS classes. This needs to be a full match
or using a relaxed regular expression. For matching partial class names, use [`method: LocatorAssertions.toContainClass`].
or using a relaxed regular expression.

```html
<div class='selected row' id='component'></div>
Expand Down
28 changes: 5 additions & 23 deletions packages/playwright-core/src/server/injected/injectedScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@ export class InjectedScript {
let received: string | undefined;
if (expression === 'to.have.attribute') {
received = element.getAttribute(options.expressionArg) || '';
} else if (expression === 'to.have.class' || expression === 'to.contain.class') {
} else if (expression === 'to.have.class') {
received = element.classList.toString();
} else if (expression === 'to.have.css') {
received = window.getComputedStyle(element).getPropertyValue(options.expressionArg);
Expand All @@ -1092,9 +1092,7 @@ export class InjectedScript {

if (received !== undefined && options.expectedText) {
const matcher = new ExpectedTextMatcher(options.expectedText[0]);
return { received, matches: matcher.matches(received, {
toContainClass: expression === 'to.contain.class',
}) };
return { received, matches: matcher.matches(received) };
}
}

Expand All @@ -1114,7 +1112,7 @@ export class InjectedScript {
let received: string[] | undefined;
if (expression === 'to.have.text.array' || expression === 'to.contain.text.array')
received = elements.map(e => options.useInnerText ? (e as HTMLElement).innerText : e.textContent || '');
else if (expression === 'to.have.class.array' || expression === 'to.contain.class.array')
else if (expression === 'to.have.class.array')
received = elements.map(e => e.classList.toString());

if (received && options.expectedText) {
Expand All @@ -1128,9 +1126,7 @@ export class InjectedScript {
const matchers = options.expectedText.map(e => new ExpectedTextMatcher(e));
let mIndex = 0, rIndex = 0;
while (mIndex < matchers.length && rIndex < received.length) {
if (matchers[mIndex].matches(received[rIndex], {
toContainClass: expression === 'to.contain.class.array',
}))
if (matchers[mIndex].matches(received[rIndex]))
++mIndex;
++rIndex;
}
Expand Down Expand Up @@ -1265,9 +1261,7 @@ class ExpectedTextMatcher {
}
}

matches(text: string, { toContainClass }: { toContainClass?: boolean } = {}): boolean {
if (toContainClass)
return this.matchesClassList(text);
matches(text: string): boolean {
if (!this._regex)
text = this.normalize(text)!;
if (this._string !== undefined)
Expand All @@ -1279,18 +1273,6 @@ class ExpectedTextMatcher {
return false;
}

private matchesClassList(received: string): boolean {
const expected = this.normalizeClassList(this._string || '');
if (expected.length === 0)
return false;
const normalizedReceived = this.normalizeClassList(received);
return expected.every(classListEntry => normalizedReceived.includes(classListEntry));
}

private normalizeClassList(classList: string): string[] {
return classList.trim().split(/\s+/g).map(c => this.normalize(c)).filter(c => c) as string[];
}

private normalize(s: string | undefined): string | undefined {
if (!s)
return s;
Expand Down
2 changes: 0 additions & 2 deletions packages/playwright-test/src/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
toContainText,
toHaveAttribute,
toHaveClass,
toContainClass,
toHaveCount,
toHaveCSS,
toHaveId,
Expand Down Expand Up @@ -135,7 +134,6 @@ const customMatchers = {
toContainText,
toHaveAttribute,
toHaveClass,
toContainClass,
toHaveCount,
toHaveCSS,
toHaveId,
Expand Down
19 changes: 0 additions & 19 deletions packages/playwright-test/src/matchers/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,25 +164,6 @@ export function toHaveClass(
}
}

export function toContainClass(
this: ReturnType<Expect['getState']>,
locator: LocatorEx,
expected: string | string[],
options?: { timeout?: number, ignoreCase?: boolean },
) {
if (Array.isArray(expected)) {
return toEqual.call(this, 'toContainClass', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
const expectedText = toExpectedTextValues(expected, { ignoreCase: options?.ignoreCase });
return await locator._expect(customStackTrace, 'to.contain.class.array', { expectedText, isNot, timeout });
}, expected, options);
} else {
return toMatchText.call(this, 'toContainClass', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
const expectedText = toExpectedTextValues([expected], { ignoreCase: options?.ignoreCase });
return await locator._expect(customStackTrace, 'to.contain.class', { expectedText, isNot, timeout });
}, expected, options);
}
}

export function toHaveCount(
this: ReturnType<Expect['getState']>,
locator: LocatorEx,
Expand Down
43 changes: 1 addition & 42 deletions packages/playwright-test/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3327,46 +3327,6 @@ interface LocatorAssertions {
timeout?: number;
}): Promise<void>;

/**
* Ensures the [Locator] points to an element that contains the given CSS class (or multiple). In contrast to
* [locatorAssertions.toHaveClass(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-class)
* which requires that the [Locator] has exactly the provided classes, `toContainClass` verifies that the [Locator] has a
* subset (or all) of the given CSS classes.
*
* ```html
* <div class='foo bar baz' id='component'>
* <div class='item alice'></div>
* <div class='item bob'></div>
* </div>
* ```
*
* ```js
* const locator = page.locator('#component');
* await expect(locator).toContainClass('bar baz'); // pass, both classes are on element
* await expect(locator).toContainClass('ba'); // fail, element has no 'ba' class
*
* const itemLocator = page.locator('#component .item');
* await expect(itemLocator).toContainClass(['alice', 'bob']); // pass, first element has alice, second bob
* await expect(itemLocator).toContainClass(['alice', 'bob carl']); // no carl class found on second item element
* await expect(itemLocator).toContainClass(['alice', 'bob', 'foobar']); // we expect 3 elements with the item class, but there are only 2
* ```
*
* Note that locator must point to a single element when passing a string or to multiple elements when passing an array.
* @param expected Expected classnames, whitespace separated. When passing an array, the given classes must be present on the locator elements.
* @param options
*/
toContainClass(expected: string|Array<string>, options?: {
/**
* Whether to perform case-insensitive match.
*/
ignoreCase?: boolean;

/**
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
*/
timeout?: number;
}): Promise<void>;

/**
* Ensures the [Locator] points to an element that contains the given text. You can use regular expressions for the value
* as well.
Expand Down Expand Up @@ -3426,8 +3386,7 @@ interface LocatorAssertions {

/**
* Ensures the [Locator] points to an element with given CSS classes. This needs to be a full match or using a relaxed
* regular expression. For matching partial class names, use
* [locatorAssertions.toContainClass(expected[, options])](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-contain-class).
* regular expression.
*
* ```html
* <div class='selected row' id='component'></div>
Expand Down
54 changes: 0 additions & 54 deletions tests/playwright-test/playwright.expect.misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,60 +262,6 @@ test('should support toHaveClass w/ array', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(1);
});

test('should support toContainClass', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
const { test } = pwt;
test('pass', async ({ page }) => {
await page.setContent(\`
<div class="foo bar baz">
<span class="alice item baz"></span>
<span class="bob item baz"></span>
</div>
\`);
const locator = page.locator('div');
await expect(locator).toContainClass('foo');
// Leading/trailing whitespace
await expect(locator).toContainClass(' foo ');
// empty should not pass
await expect(locator).not.toContainClass('');
await expect(locator).toContainClass('bar');
await expect(locator).toContainClass('baz');
await expect(locator).toContainClass('foo baz');
await expect(locator).toContainClass('baz foo');
await expect(locator).not.toContainClass('ba');
await expect(locator).toContainClass('BAZ FoO', { ignoreCase: true });
await expect(locator).not.toContainClass('BAZ');
const locatorSpan = page.locator('div span');
await expect(locatorSpan).toContainClass(['alice baz', 'bob']);
await expect(locatorSpan).not.toContainClass(['alice', 'alice']);
});
test('fail', async ({ page }) => {
await page.setContent('<div class="bar baz"></div>');
const locator = page.locator('div');
await expect(locator).toContainClass('foo', { timeout: 1000 });
});
test('fail length mismatch', async ({ page }) => {
await page.setContent('<div><span class="alice"></span><span class="bob"></span></div>');
const locator = page.locator('div span');
await expect(locator).toContainClass('alice', { timeout: 1000 });
});
`,
}, { workers: 1 });
const output = stripAnsi(result.output);
expect(output).toContain('expect(locator).toContainClass');
expect(output).toContain('Expected string: \"foo\"');
expect(output).toContain('Received string: \"bar baz\"');
expect(result.passed).toBe(1);
expect(result.failed).toBe(2);
expect(result.exitCode).toBe(1);
});

test('should support toHaveTitle', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
Expand Down

0 comments on commit b2e3c8c

Please sign in to comment.