diff --git a/docs/src/api/params.md b/docs/src/api/params.md
index 441ad4e544..c2c9e33c0d 100644
--- a/docs/src/api/params.md
+++ b/docs/src/api/params.md
@@ -1023,9 +1023,11 @@ For example, `"Playwright"` matches `Playwright
`.
## locator-option-has
- `has` <[Locator]>
-Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
+Narrows down the results of the method to those which contain elements matching this relative locator.
For example, `article` that has `text=Playwright` matches `Playwright
`.
+Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the document root. For example, you can find `content` that has `div` in `Playwright
`. However, looking for `content` that has `article div` will fail, because the inner locator must be relative and should not use any elements outside the `content`.
+
Note that outer and inner locators must belong to the same frame. Inner locator must not contain [FrameLocator]s.
## locator-option-has-not
diff --git a/docs/src/locators.md b/docs/src/locators.md
index ee036dbf79..720e5618ca 100644
--- a/docs/src/locators.md
+++ b/docs/src/locators.md
@@ -973,19 +973,21 @@ await page
.ClickAsync();
```
-We can also assert the product card to make sure there is only one
+We can also assert the product card to make sure there is only one:
```js
await expect(page
.getByRole('listitem')
- .filter({ has: page.getByText('Product 2') }))
+ .filter({ has: page.getByRole('heading', { name: 'Product 2' }) }))
.toHaveCount(1);
```
```java
assertThat(page
.getByRole(AriaRole.LISTITEM)
- .filter(new Locator.FilterOptions().setHas(page.getByText("Product 2")))
+ .filter(new Locator.FilterOptions()
+ .setHas(page.GetByRole(AriaRole.HEADING,
+ new Page.GetByRoleOptions().setName("Product 2"))))
.hasCount(1);
```
@@ -1014,6 +1016,55 @@ await Expect(Page
.ToHaveCountAsync(1);
```
+The filtering locator **must be relative** to the original locator and is queried starting with the original locator match, not the document root. Therefore, the following will not work, because the filtering locator starts matching from the `
` list element that is outside of the `- ` list item matched by the original locator:
+
+```js
+// ✖ WRONG
+await expect(page
+ .getByRole('listitem')
+ .filter({ has: page.getByRole('list').getByText('Product 2') }))
+ .toHaveCount(1);
+```
+
+```java
+// ✖ WRONG
+assertThat(page
+ .getByRole(AriaRole.LISTITEM)
+ .filter(new Locator.FilterOptions()
+ .setHas(page.GetByRole(AriaRole.LIST)
+ .GetByRole(AriaRole.HEADING,
+ new Page.GetByRoleOptions().setName("Product 2"))))
+ .hasCount(1);
+```
+
+```python async
+# ✖ WRONG
+await expect(
+ page.get_by_role("listitem").filter(
+ has=page.get_by_role("list").get_by_role("heading", name="Product 2")
+ )
+).to_have_count(1)
+```
+
+```python sync
+# ✖ WRONG
+expect(
+ page.get_by_role("listitem").filter(
+ has=page.get_by_role("list").get_by_role("heading", name="Product 2")
+ )
+).to_have_count(1)
+```
+
+```csharp
+// ✖ WRONG
+await Expect(Page
+ .GetByRole(AriaRole.Listitem)
+ .Filter(new() {
+ Has = page.GetByRole(AriaRole.List).GetByRole(AriaRole.Heading, new() { Name = "Product 2" })
+ }))
+ .ToHaveCountAsync(1);
+```
+
### Filter by not having child/descendant
We can also filter by **not having** a matching element inside.
diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts
index ffb24f26aa..0a6fea4859 100644
--- a/packages/playwright-core/types/types.d.ts
+++ b/packages/playwright-core/types/types.d.ts
@@ -3243,8 +3243,13 @@ export interface Page {
*/
locator(selector: string, options?: {
/**
- * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer
- * one. For example, `article` that has `text=Playwright` matches `
Playwright
`.
+ * Narrows down the results of the method to those which contain elements matching this relative locator. For example,
+ * `article` that has `text=Playwright` matches `Playwright
`.
+ *
+ * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not
+ * the document root. For example, you can find `content` that has `div` in
+ * `Playwright
`. However, looking for `content` that has `article
+ * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link
* FrameLocator}s.
@@ -6676,8 +6681,13 @@ export interface Frame {
*/
locator(selector: string, options?: {
/**
- * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer
- * one. For example, `article` that has `text=Playwright` matches `Playwright
`.
+ * Narrows down the results of the method to those which contain elements matching this relative locator. For example,
+ * `article` that has `text=Playwright` matches `Playwright
`.
+ *
+ * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not
+ * the document root. For example, you can find `content` that has `div` in
+ * `Playwright
`. However, looking for `content` that has `article
+ * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link
* FrameLocator}s.
@@ -11252,8 +11262,13 @@ export interface Locator {
*/
filter(options?: {
/**
- * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer
- * one. For example, `article` that has `text=Playwright` matches `Playwright
`.
+ * Narrows down the results of the method to those which contain elements matching this relative locator. For example,
+ * `article` that has `text=Playwright` matches `Playwright
`.
+ *
+ * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not
+ * the document root. For example, you can find `content` that has `div` in
+ * `Playwright
`. However, looking for `content` that has `article
+ * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link
* FrameLocator}s.
@@ -11956,8 +11971,13 @@ export interface Locator {
*/
locator(selectorOrLocator: string|Locator, options?: {
/**
- * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer
- * one. For example, `article` that has `text=Playwright` matches `Playwright
`.
+ * Narrows down the results of the method to those which contain elements matching this relative locator. For example,
+ * `article` that has `text=Playwright` matches `Playwright
`.
+ *
+ * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not
+ * the document root. For example, you can find `content` that has `div` in
+ * `Playwright
`. However, looking for `content` that has `article
+ * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link
* FrameLocator}s.
@@ -17735,8 +17755,13 @@ export interface FrameLocator {
*/
locator(selectorOrLocator: string|Locator, options?: {
/**
- * Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer
- * one. For example, `article` that has `text=Playwright` matches `Playwright
`.
+ * Narrows down the results of the method to those which contain elements matching this relative locator. For example,
+ * `article` that has `text=Playwright` matches `Playwright
`.
+ *
+ * Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not
+ * the document root. For example, you can find `content` that has `div` in
+ * `Playwright
`. However, looking for `content` that has `article
+ * div` will fail, because the inner locator must be relative and should not use any elements outside the `content`.
*
* Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@link
* FrameLocator}s.