Compare commits
19 commits
main
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd8a1193fc | ||
|
|
87a6f9cb77 | ||
|
|
e164ac7697 | ||
|
|
1a80286a4d | ||
|
|
f822d650b2 | ||
|
|
9749f75426 | ||
|
|
d4ac444f7d | ||
|
|
a7025956c3 | ||
|
|
b67050638b | ||
|
|
3a4381303c | ||
|
|
d23fb005d4 | ||
|
|
56e50a7304 | ||
|
|
3f6b6419c5 | ||
|
|
20db86da3e | ||
|
|
84780b1b75 | ||
|
|
e7f0635c17 | ||
|
|
8709a3a24b | ||
|
|
aa9f6fb718 | ||
|
|
f5899c1556 |
15
.eslintrc-with-ts-config.js
Normal file
15
.eslintrc-with-ts-config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: "./.eslintrc.js",
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 9,
|
||||||
|
sourceType: "module",
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-base-to-string": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-boolean-literal-compare": 2,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
project: "./tsconfig.json"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -4,7 +4,6 @@ module.exports = {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 9,
|
ecmaVersion: 9,
|
||||||
sourceType: "module",
|
sourceType: "module",
|
||||||
project: "./tsconfig.json",
|
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
"plugin:react-hooks/recommended"
|
"plugin:react-hooks/recommended"
|
||||||
|
|
@ -49,6 +48,7 @@ module.exports = {
|
||||||
"arrow-parens": [2, "as-needed"],
|
"arrow-parens": [2, "as-needed"],
|
||||||
"prefer-const": 2,
|
"prefer-const": 2,
|
||||||
"quote-props": [2, "consistent"],
|
"quote-props": [2, "consistent"],
|
||||||
|
"nonblock-statement-body-position": [2, "below"],
|
||||||
|
|
||||||
// anti-patterns
|
// anti-patterns
|
||||||
"no-var": 2,
|
"no-var": 2,
|
||||||
|
|
|
||||||
|
|
@ -238,3 +238,5 @@ public class HomepageTests extends AxeTestFixtures {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See experimental [JUnit integration](./junit.md) to automatically initialize Playwright objects and more.
|
||||||
|
|
|
||||||
|
|
@ -375,6 +375,8 @@ public class TestGitHubAPI {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See experimental [JUnit integration](./junit.md) to automatically initialize Playwright objects and more.
|
||||||
|
|
||||||
## Prepare server state via API calls
|
## Prepare server state via API calls
|
||||||
|
|
||||||
The following test creates a new issue via API and then navigates to the list of all issues in the
|
The following test creates a new issue via API and then navigates to the list of all issues in the
|
||||||
|
|
|
||||||
|
|
@ -3146,32 +3146,38 @@ return value resolves to `[]`.
|
||||||
## async method: Page.addLocatorHandler
|
## async method: Page.addLocatorHandler
|
||||||
* since: v1.42
|
* since: v1.42
|
||||||
|
|
||||||
Sometimes, the web page can show an overlay that obstructs elements behind it and prevents certain actions, like click, from completing. When such an overlay is shown predictably, we recommend dismissing it as a part of your test flow. However, sometimes such an overlay may appear non-deterministically, for example certain cookies consent dialogs behave this way. In this case, [`method: Page.addLocatorHandler`] allows handling an overlay during an action that it would block.
|
:::warning Experimental
|
||||||
|
This method is experimental and its behavior may change in the upcoming releases.
|
||||||
|
:::
|
||||||
|
|
||||||
This method registers a handler for an overlay that is executed once the locator is visible on the page. The handler should get rid of the overlay so that actions blocked by it can proceed. This is useful for nondeterministic interstitial pages or dialogs, like a cookie consent dialog.
|
When testing a web page, sometimes unexpected overlays like a "Sign up" dialog appear and block actions you want to automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making them tricky to handle in automated tests.
|
||||||
|
|
||||||
Note that execution time of the handler counts towards the timeout of the action/assertion that executed the handler.
|
This method lets you set up a special function, called a handler, that activates when it detects that overlay is visible. The handler's job is to remove the overlay, allowing your test to continue as if the overlay wasn't there.
|
||||||
|
|
||||||
You can register multiple handlers. However, only a single handler will be running at a time. Any actions inside a handler must not require another handler to run.
|
Things to keep in mind:
|
||||||
|
* When an overlay is shown predictably, we recommend explicitly waiting for it in your test and dismissing it as a part of your normal test flow, instead of using [`method: Page.addLocatorHandler`].
|
||||||
|
* Playwright checks for the overlay every time before executing or retrying an action that requires an [actionability check](../actionability.md), or before performing an auto-waiting assertion check. When overlay is visible, Playwright calls the handler first, and then proceeds with the action/assertion.
|
||||||
|
* The execution time of the handler counts towards the timeout of the action/assertion that executed the handler. If your handler takes too long, it might cause timeouts.
|
||||||
|
* You can register multiple handlers. However, only a single handler will be running at a time. Make sure the actions within a handler don't depend on another handler.
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
Running the interceptor will alter your page state mid-test. For example it will change the currently focused element and move the mouse. Make sure that the actions that run after the interceptor are self-contained and do not rely on the focus and mouse state.
|
Running the handler will alter your page state mid-test. For example it will change the currently focused element and move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and mouse state being unchanged.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
For example, consider a test that calls [`method: Locator.focus`] followed by [`method: Keyboard.press`]. If your handler clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen on the unexpected element. Use [`method: Locator.press`] instead to avoid this problem.
|
For example, consider a test that calls [`method: Locator.focus`] followed by [`method: Keyboard.press`]. If your handler clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen on the unexpected element. Use [`method: Locator.press`] instead to avoid this problem.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Another example is a series of mouse actions, where [`method: Mouse.move`] is followed by [`method: Mouse.down`]. Again, when the handler runs between these two actions, the mouse position will be wrong during the mouse down. Prefer methods like [`method: Locator.click`] that are self-contained.
|
Another example is a series of mouse actions, where [`method: Mouse.move`] is followed by [`method: Mouse.down`]. Again, when the handler runs between these two actions, the mouse position will be wrong during the mouse down. Prefer self-contained actions like [`method: Locator.click`] that do not rely on the state being unchanged by a handler.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
**Usage**
|
**Usage**
|
||||||
|
|
||||||
An example that closes a cookie dialog when it appears:
|
An example that closes a "Sign up to the newsletter" dialog when it appears:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Setup the handler.
|
// Setup the handler.
|
||||||
await page.addLocatorHandler(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
|
await page.addLocatorHandler(page.getByText('Sign up to the newsletter'), async () => {
|
||||||
await page.getByRole('button', { name: 'Reject all cookies' }).click();
|
await page.getByRole('button', { name: 'No thanks' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write the test as usual.
|
// Write the test as usual.
|
||||||
|
|
@ -3181,8 +3187,8 @@ await page.getByRole('button', { name: 'Start here' }).click();
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// Setup the handler.
|
// Setup the handler.
|
||||||
page.addLocatorHandler(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all cookies")), () => {
|
page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () => {
|
||||||
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Reject all cookies")).click();
|
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write the test as usual.
|
// Write the test as usual.
|
||||||
|
|
@ -3193,8 +3199,8 @@ page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
|
||||||
```python sync
|
```python sync
|
||||||
# Setup the handler.
|
# Setup the handler.
|
||||||
def handler():
|
def handler():
|
||||||
page.get_by_role("button", name="Reject all cookies").click()
|
page.get_by_role("button", name="No thanks").click()
|
||||||
page.add_locator_handler(page.get_by_role("button", name="Accept all cookies"), handler)
|
page.add_locator_handler(page.get_by_text("Sign up to the newsletter"), handler)
|
||||||
|
|
||||||
# Write the test as usual.
|
# Write the test as usual.
|
||||||
page.goto("https://example.com")
|
page.goto("https://example.com")
|
||||||
|
|
@ -3204,8 +3210,8 @@ page.get_by_role("button", name="Start here").click()
|
||||||
```python async
|
```python async
|
||||||
# Setup the handler.
|
# Setup the handler.
|
||||||
def handler():
|
def handler():
|
||||||
await page.get_by_role("button", name="Reject all cookies").click()
|
await page.get_by_role("button", name="No thanks").click()
|
||||||
await page.add_locator_handler(page.get_by_role("button", name="Accept all cookies"), handler)
|
await page.add_locator_handler(page.get_by_text("Sign up to the newsletter"), handler)
|
||||||
|
|
||||||
# Write the test as usual.
|
# Write the test as usual.
|
||||||
await page.goto("https://example.com")
|
await page.goto("https://example.com")
|
||||||
|
|
@ -3214,8 +3220,8 @@ await page.get_by_role("button", name="Start here").click()
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Setup the handler.
|
// Setup the handler.
|
||||||
await page.AddLocatorHandlerAsync(page.GetByRole(AriaRole.Button, new() { Name = "Accept all cookies" }), async () => {
|
await page.AddLocatorHandlerAsync(page.GetByText("Sign up to the newsletter"), async () => {
|
||||||
await page.GetByRole(AriaRole.Button, new() { Name = "Reject all cookies" }).ClickAsync();
|
await page.GetByRole(AriaRole.Button, new() { Name = "No thanks" }).ClickAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write the test as usual.
|
// Write the test as usual.
|
||||||
|
|
@ -3228,7 +3234,7 @@ An example that skips the "Confirm your security details" page when it is shown:
|
||||||
```js
|
```js
|
||||||
// Setup the handler.
|
// Setup the handler.
|
||||||
await page.addLocatorHandler(page.getByText('Confirm your security details'), async () => {
|
await page.addLocatorHandler(page.getByText('Confirm your security details'), async () => {
|
||||||
await page.getByRole('button', 'Remind me later').click();
|
await page.getByRole('button', { name: 'Remind me later' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write the test as usual.
|
// Write the test as usual.
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ existing authentication state instead.
|
||||||
Playwright provides a way to reuse the signed-in state in the tests. That way you can log
|
Playwright provides a way to reuse the signed-in state in the tests. That way you can log
|
||||||
in only once and then skip the log in step for all of the tests.
|
in only once and then skip the log in step for all of the tests.
|
||||||
|
|
||||||
Web apps use cookie-based or token-based authentication, where authenticated state is stored as [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) or in [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage). Playwright provides [browserContext.storageState([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state) method that can be used to retrieve storage state from authenticated contexts and then create new contexts with pre-populated state.
|
Web apps use cookie-based or token-based authentication, where authenticated state is stored as [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) or in [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage). Playwright provides [`method: BrowserContext.storageState`] method that can be used to retrieve storage state from authenticated contexts and then create new contexts with pre-populated state.
|
||||||
|
|
||||||
Cookies and local storage state can be used across different browsers. They depend on your application's authentication model: some apps might require both cookies and local storage.
|
Cookies and local storage state can be used across different browsers. They depend on your application's authentication model: some apps might require both cookies and local storage.
|
||||||
|
|
||||||
|
|
|
||||||
180
docs/src/junit-java.md
Normal file
180
docs/src/junit-java.md
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
---
|
||||||
|
id: junit
|
||||||
|
title: "JUnit (experimental)"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
With a few lines of code, you can hook up Playwright to your favorite Java test runner.
|
||||||
|
|
||||||
|
In [JUnit](https://junit.org/junit5/), you can use Playwright [fixtures](./junit.md#fixtures) to automatically initialize [Playwright], [Browser], [BrowserContext] or [Page]. In the example below, all three test methods use the same
|
||||||
|
[Browser]. Each test uses its own [BrowserContext] and [Page].
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
|
```java
|
||||||
|
package org.example;
|
||||||
|
|
||||||
|
import com.microsoft.playwright.Page;
|
||||||
|
import com.microsoft.playwright.junit.UsePlaywright;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
@UsePlaywright
|
||||||
|
public class TestExample {
|
||||||
|
@Test
|
||||||
|
void shouldClickButton(Page page) {
|
||||||
|
page.navigate("data:text/html,<script>var result;</script><button onclick='result=\"Clicked\"'>Go</button>");
|
||||||
|
page.locator("button").click();
|
||||||
|
assertEquals("Clicked", page.evaluate("result"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCheckTheBox(Page page) {
|
||||||
|
page.setContent("<input id='checkbox' type='checkbox'></input>");
|
||||||
|
page.locator("input").check();
|
||||||
|
assertEquals(true, page.evaluate("window['checkbox'].checked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSearchWiki(Page page) {
|
||||||
|
page.navigate("https://www.wikipedia.org/");
|
||||||
|
page.locator("input[name=\"search\"]").click();
|
||||||
|
page.locator("input[name=\"search\"]").fill("playwright");
|
||||||
|
page.locator("input[name=\"search\"]").press("Enter");
|
||||||
|
assertThat(page).hasURL("https://en.wikipedia.org/wiki/Playwright");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fixtures
|
||||||
|
|
||||||
|
Simply add JUnit annotation `@UsePlaywright` to your test classes to enable Playwright fixtures. Test fixtures are used to establish environment for each test, giving the test everything it needs and nothing else.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@UsePlaywright
|
||||||
|
public class TestExample {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void basicTest(Page page) {
|
||||||
|
page.navigate("https://playwright.dev/");
|
||||||
|
|
||||||
|
assertThat(page).hasTitle(Pattern.compile("Playwright"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Page page` argument tells JUnit to setup the `page` fixture and provide it to your test method.
|
||||||
|
|
||||||
|
Here is a list of the pre-defined fixtures:
|
||||||
|
|
||||||
|
|Fixture |Type |Description |
|
||||||
|
|:-------------|:------------------|:--------------------------------|
|
||||||
|
|page |[Page] |Isolated page for this test run.|
|
||||||
|
|browserContext|[BrowserContext] |Isolated context for this test run. The `page` fixture belongs to this context as well.|
|
||||||
|
|browser |[Browser] |Browsers are shared across tests to optimize resources.|
|
||||||
|
|playwright |[Playwright] |Playwright instance is shared between tests running on the same thread.|
|
||||||
|
|request |[APIRequestContext]|Isolated APIRequestContext for this test run. Learn how to do [API testing](./api-testing).|
|
||||||
|
|
||||||
|
## Customizing options
|
||||||
|
|
||||||
|
To customize fixture options, you should implement an `OptionsFactory` and specify the class in the `@UsePlaywright()` annotation.
|
||||||
|
|
||||||
|
You can easily override launch options for [`method: BrowserType.launch`], or context options for [`method: Browser.newContext`] and [`method: APIRequest.newContext`]. See the following example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.microsoft.playwright.junit.Options;
|
||||||
|
import com.microsoft.playwright.junit.OptionsFactory;
|
||||||
|
import com.microsoft.playwright.junit.UsePlaywright;
|
||||||
|
|
||||||
|
@UsePlaywright(MyTest.CustomOptions.class)
|
||||||
|
public class MyTest {
|
||||||
|
|
||||||
|
public static class CustomOptions implements OptionsFactory {
|
||||||
|
@Override
|
||||||
|
public Options getOptions() {
|
||||||
|
return new Options()
|
||||||
|
.setHeadless(false)
|
||||||
|
.setContextOption(new Browser.NewContextOptions()
|
||||||
|
.setBaseURL("https://github.com"))
|
||||||
|
.setApiRequestOptions(new APIRequest.NewContextOptions()
|
||||||
|
.setBaseURL("https://playwright.dev"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithCustomOptions(Page page, APIRequestContext request) {
|
||||||
|
page.navigate("/");
|
||||||
|
assertThat(page).hasURL(Pattern.compile("github"));
|
||||||
|
|
||||||
|
APIResponse response = request.get("/");
|
||||||
|
assertTrue(response.text().contains("Playwright"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Tests in Parallel
|
||||||
|
|
||||||
|
By default JUnit will run all tests sequentially on a single thread. Since JUnit 5.3 you can change this behavior to run tests in parallel
|
||||||
|
to speed up execution (see [this page](https://junit.org/junit5/docs/snapshot/user-guide/index.html#writing-tests-parallel-execution)).
|
||||||
|
Since it is not safe to use same Playwright objects from multiple threads without extra synchronization we recommend you create Playwright
|
||||||
|
instance per thread and use it on that thread exclusively. Here is an example how to run multiple test classes in parallel.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@UsePlaywright
|
||||||
|
class Test1 {
|
||||||
|
@Test
|
||||||
|
void shouldClickButton(Page page) {
|
||||||
|
page.navigate("data:text/html,<script>var result;</script><button onclick='result=\"Clicked\"'>Go</button>");
|
||||||
|
page.locator("button").click();
|
||||||
|
assertEquals("Clicked", page.evaluate("result"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCheckTheBox(Page page) {
|
||||||
|
page.setContent("<input id='checkbox' type='checkbox'></input>");
|
||||||
|
page.locator("input").check();
|
||||||
|
assertEquals(true, page.evaluate("window['checkbox'].checked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSearchWiki(Page page) {
|
||||||
|
page.navigate("https://www.wikipedia.org/");
|
||||||
|
page.locator("input[name=\"search\"]").click();
|
||||||
|
page.locator("input[name=\"search\"]").fill("playwright");
|
||||||
|
page.locator("input[name=\"search\"]").press("Enter");
|
||||||
|
assertThat(page).hasURL("https://en.wikipedia.org/wiki/Playwright");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UsePlaywright
|
||||||
|
class Test2 {
|
||||||
|
@Test
|
||||||
|
void shouldReturnInnerHTML(Page page) {
|
||||||
|
page.setContent("<div>hello</div>");
|
||||||
|
assertEquals("hello", page.innerHTML("css=div"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldClickButton(Page page) {
|
||||||
|
Page popup = page.waitForPopup(() -> {
|
||||||
|
page.evaluate("window.open('about:blank');");
|
||||||
|
});
|
||||||
|
assertEquals("about:blank", popup.url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Configure JUnit to run tests in each class sequentially and run multiple classes on parallel threads (with max
|
||||||
|
number of thread equal to 1/2 of the number of CPU cores):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
junit.jupiter.execution.parallel.enabled = true
|
||||||
|
junit.jupiter.execution.parallel.mode.default = same_thread
|
||||||
|
junit.jupiter.execution.parallel.mode.classes.default = concurrent
|
||||||
|
junit.jupiter.execution.parallel.config.strategy=dynamic
|
||||||
|
junit.jupiter.execution.parallel.config.dynamic.factor=0.5
|
||||||
|
```
|
||||||
|
|
@ -4,6 +4,45 @@ title: "Release notes"
|
||||||
toc_max_heading_level: 2
|
toc_max_heading_level: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Version 1.42
|
||||||
|
|
||||||
|
### New Locator Handler
|
||||||
|
|
||||||
|
New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Setup the handler.
|
||||||
|
await Page.AddLocatorHandlerAsync(
|
||||||
|
Page.GetByRole(AriaRole.Heading, new() { Name = "Hej! You are in control of your cookies." }),
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await Page.GetByRole(AriaRole.Button, new() { Name = "Accept all" }).ClickAsync();
|
||||||
|
});
|
||||||
|
// Write the test as usual.
|
||||||
|
await Page.GotoAsync("https://www.ikea.com/");
|
||||||
|
await Page.GetByRole(AriaRole.Link, new() { Name = "Collection of blue and white" }).ClickAsync();
|
||||||
|
await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Light and easy" })).ToBeVisibleAsync();
|
||||||
|
```
|
||||||
|
|
||||||
|
### New APIs
|
||||||
|
|
||||||
|
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||||
|
|
||||||
|
### Announcements
|
||||||
|
|
||||||
|
* ⚠️ Ubuntu 18 is not supported anymore.
|
||||||
|
|
||||||
|
### Browser Versions
|
||||||
|
|
||||||
|
* Chromium 121.0.6167.57
|
||||||
|
* Mozilla Firefox 121.0
|
||||||
|
* WebKit 17.4
|
||||||
|
|
||||||
|
This version was also tested against the following stable channels:
|
||||||
|
|
||||||
|
* Google Chrome 120
|
||||||
|
* Microsoft Edge 120
|
||||||
|
|
||||||
## Version 1.41
|
## Version 1.41
|
||||||
|
|
||||||
### New APIs
|
### New APIs
|
||||||
|
|
@ -511,7 +550,7 @@ This version was also tested against the following stable channels:
|
||||||
|
|
||||||
### New .runsettings file support
|
### New .runsettings file support
|
||||||
|
|
||||||
`Microsoft.Playwright.NUnit` and `Microsoft.Playwright.MSTest` will now consider the `.runsettings` file and passed settings via the CLI when running end-to-end tests. See in the [documentation](https://playwright.dev/dotnet/docs/test-runners) for a full list of supported settings.
|
`Microsoft.Playwright.NUnit` and `Microsoft.Playwright.MSTest` will now consider the `.runsettings` file and passed settings via the CLI when running end-to-end tests. See in the [documentation](./test-runners) for a full list of supported settings.
|
||||||
|
|
||||||
The following does now work:
|
The following does now work:
|
||||||
|
|
||||||
|
|
@ -574,7 +613,7 @@ Linux support looks like this:
|
||||||
|
|
||||||
### New introduction docs
|
### New introduction docs
|
||||||
|
|
||||||
We rewrote our Getting Started docs to be more end-to-end testing focused. Check them out on [playwright.dev](https://playwright.dev/dotnet/docs/intro).
|
We rewrote our Getting Started docs to be more end-to-end testing focused. Check them out on [playwright.dev](./intro).
|
||||||
|
|
||||||
## Version 1.23
|
## Version 1.23
|
||||||
|
|
||||||
|
|
@ -952,31 +991,31 @@ This version of Playwright was also tested against the following stable channels
|
||||||
|
|
||||||
### 🖱️ Mouse Wheel
|
### 🖱️ Mouse Wheel
|
||||||
|
|
||||||
By using [`Page.Mouse.WheelAsync`](https://playwright.dev/dotnet/docs/next/api/class-mouse#mouse-wheel) you are now able to scroll vertically or horizontally.
|
By using [`method: Mouse.wheel`] you are now able to scroll vertically or horizontally.
|
||||||
|
|
||||||
### 📜 New Headers API
|
### 📜 New Headers API
|
||||||
|
|
||||||
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
||||||
|
|
||||||
- [Request.AllHeadersAsync()](https://playwright.dev/dotnet/docs/next/api/class-request#request-all-headers)
|
- [`method: Request.allHeaders`]
|
||||||
- [Request.HeadersArrayAsync()](https://playwright.dev/dotnet/docs/next/api/class-request#request-headers-array)
|
- [`method: Request.headersArray`]
|
||||||
- [Request.HeaderValueAsync(name: string)](https://playwright.dev/dotnet/docs/next/api/class-request#request-header-value)
|
- [`method: Request.headerValue`]
|
||||||
- [Response.AllHeadersAsync()](https://playwright.dev/dotnet/docs/next/api/class-response#response-all-headers)
|
- [`method: Response.allHeaders`]
|
||||||
- [Response.HeadersArrayAsync()](https://playwright.dev/dotnet/docs/next/api/class-response#response-headers-array)
|
- [`method: Response.headersArray`]
|
||||||
- [Response.HeaderValueAsync(name: string)](https://playwright.dev/dotnet/docs/next/api/class-response#response-header-value)
|
- [`method: Response.headerValue`]
|
||||||
- [Response.HeaderValuesAsync(name: string)](https://playwright.dev/dotnet/docs/next/api/class-response#response-header-values)
|
- [`method: Response.headerValues`]
|
||||||
|
|
||||||
### 🌈 Forced-Colors emulation
|
### 🌈 Forced-Colors emulation
|
||||||
|
|
||||||
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [context options](https://playwright.dev/dotnet/docs/next/api/class-browser#browser-new-context-option-forced-colors) or calling [Page.EmulateMediaAsync()](https://playwright.dev/dotnet/docs/next/api/class-page#page-emulate-media).
|
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [`method: Browser.newContext`] or calling [`method: Page.emulateMedia`].
|
||||||
|
|
||||||
### New APIs
|
### New APIs
|
||||||
|
|
||||||
- [Page.RouteAsync()](https://playwright.dev/dotnet/docs/next/api/class-page#page-route) accepts new `times` option to specify how many times this route should be matched.
|
- [`method: Page.route`] accepts new `times` option to specify how many times this route should be matched.
|
||||||
- [Page.SetCheckedAsync(selector: string, checked: Boolean)](https://playwright.dev/dotnet/docs/next/api/class-page#page-set-checked) and [Locator.SetCheckedAsync(selector: string, checked: Boolean)](https://playwright.dev/dotnet/docs/next/api/class-locator#locator-set-checked) was introduced to set the checked state of a checkbox.
|
- [`method: Page.setChecked`] and [`method: Locator.setChecked`] were introduced to set the checked state of a checkbox.
|
||||||
- [Request.SizesAsync()](https://playwright.dev/dotnet/docs/next/api/class-request#request-sizes) Returns resource size information for given http request.
|
- [`method: Request.sizes`] Returns resource size information for given http request.
|
||||||
- [Tracing.StartChunkAsync()](https://playwright.dev/dotnet/docs/next/api/class-tracing#tracing-start-chunk) - Start a new trace chunk.
|
- [`method: Tracing.startChunk`] - Start a new trace chunk.
|
||||||
- [Tracing.StopChunkAsync()](https://playwright.dev/dotnet/docs/next/api/class-tracing#tracing-stop-chunk) - Stops a new trace chunk.
|
- [`method: Tracing.stopChunk`] - Stops a new trace chunk.
|
||||||
|
|
||||||
### Important ⚠
|
### Important ⚠
|
||||||
* ⬆ .NET Core Apps 2.1 are **no longer** supported for our CLI tooling. As of August 31st, 2021, .NET Core 2.1 is no [longer supported](https://devblogs.microsoft.com/dotnet/net-core-2-1-will-reach-end-of-support-on-august-21-2021/) and will not receive any security updates. We've decided to move the CLI forward and require .NET Core 3.1 as a minimum.
|
* ⬆ .NET Core Apps 2.1 are **no longer** supported for our CLI tooling. As of August 31st, 2021, .NET Core 2.1 is no [longer supported](https://devblogs.microsoft.com/dotnet/net-core-2-1-will-reach-end-of-support-on-august-21-2021/) and will not receive any security updates. We've decided to move the CLI forward and require .NET Core 3.1 as a minimum.
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,126 @@ title: "Release notes"
|
||||||
toc_max_heading_level: 2
|
toc_max_heading_level: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Version 1.42
|
||||||
|
|
||||||
|
### Experimental JUnit integration
|
||||||
|
|
||||||
|
Add new [`@UsePlaywright`](./junit.md) annotation to your test classes to start using Playwright
|
||||||
|
fixtures for [Page], [BrowserContext], [Browser], [APIRequestContext] and [Playwright] in the
|
||||||
|
test methods.
|
||||||
|
|
||||||
|
```java
|
||||||
|
package org.example;
|
||||||
|
|
||||||
|
import com.microsoft.playwright.Page;
|
||||||
|
import com.microsoft.playwright.junit.UsePlaywright;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
@UsePlaywright
|
||||||
|
public class TestExample {
|
||||||
|
void shouldNavigateToInstallationGuide(Page page) {
|
||||||
|
page.navigate("https://playwright.dev/java/");
|
||||||
|
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Docs")).click();
|
||||||
|
assertThat(page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setName("Installation"))).isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCheckTheBox(Page page) {
|
||||||
|
page.setContent("<input id='checkbox' type='checkbox'></input>");
|
||||||
|
page.locator("input").check();
|
||||||
|
assertEquals(true, page.evaluate("window['checkbox'].checked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSearchWiki(Page page) {
|
||||||
|
page.navigate("https://www.wikipedia.org/");
|
||||||
|
page.locator("input[name=\"search\"]").click();
|
||||||
|
page.locator("input[name=\"search\"]").fill("playwright");
|
||||||
|
page.locator("input[name=\"search\"]").press("Enter");
|
||||||
|
assertThat(page).hasURL("https://en.wikipedia.org/wiki/Playwright");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example above, all three test methods use the same [Browser]. Each test
|
||||||
|
uses its own [BrowserContext] and [Page].
|
||||||
|
|
||||||
|
**Custom options**
|
||||||
|
|
||||||
|
Implement your own `OptionsFactory` to initialize the fixtures with custom configuration.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.microsoft.playwright.junit.Options;
|
||||||
|
import com.microsoft.playwright.junit.OptionsFactory;
|
||||||
|
import com.microsoft.playwright.junit.UsePlaywright;
|
||||||
|
|
||||||
|
@UsePlaywright(MyTest.CustomOptions.class)
|
||||||
|
public class MyTest {
|
||||||
|
|
||||||
|
public static class CustomOptions implements OptionsFactory {
|
||||||
|
@Override
|
||||||
|
public Options getOptions() {
|
||||||
|
return new Options()
|
||||||
|
.setHeadless(false)
|
||||||
|
.setContextOption(new Browser.NewContextOptions()
|
||||||
|
.setBaseURL("https://github.com"))
|
||||||
|
.setApiRequestOptions(new APIRequest.NewContextOptions()
|
||||||
|
.setBaseURL("https://playwright.dev"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithCustomOptions(Page page, APIRequestContext request) {
|
||||||
|
page.navigate("/");
|
||||||
|
assertThat(page).hasURL(Pattern.compile("github"));
|
||||||
|
|
||||||
|
APIResponse response = request.get("/");
|
||||||
|
assertTrue(response.text().contains("Playwright"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more about the fixtures in our [JUnit guide](./junit.md).
|
||||||
|
|
||||||
|
### New Locator Handler
|
||||||
|
|
||||||
|
New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears.
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Setup the handler.
|
||||||
|
page.addLocatorHandler(
|
||||||
|
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Hej! You are in control of your cookies.")),
|
||||||
|
() - > {
|
||||||
|
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all")).click();
|
||||||
|
});
|
||||||
|
// Write the test as usual.
|
||||||
|
page.navigate("https://www.ikea.com/");
|
||||||
|
page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("Collection of blue and white")).click();
|
||||||
|
assertThat(page.getByRole(AriaRole.HEADING, new Page.GetByRoleOptions().setName("Light and easy"))).isVisible();
|
||||||
|
```
|
||||||
|
|
||||||
|
### New APIs
|
||||||
|
|
||||||
|
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||||
|
|
||||||
|
### Announcements
|
||||||
|
|
||||||
|
* ⚠️ Ubuntu 18 is not supported anymore.
|
||||||
|
|
||||||
|
### Browser Versions
|
||||||
|
|
||||||
|
* Chromium 121.0.6167.57
|
||||||
|
* Mozilla Firefox 121.0
|
||||||
|
* WebKit 17.4
|
||||||
|
|
||||||
|
This version was also tested against the following stable channels:
|
||||||
|
|
||||||
|
* Google Chrome 120
|
||||||
|
* Microsoft Edge 120
|
||||||
|
|
||||||
## Version 1.41
|
## Version 1.41
|
||||||
|
|
||||||
### New APIs
|
### New APIs
|
||||||
|
|
@ -931,31 +1051,31 @@ This version of Playwright was also tested against the following stable channels
|
||||||
|
|
||||||
### 🖱️ Mouse Wheel
|
### 🖱️ Mouse Wheel
|
||||||
|
|
||||||
By using [`Mouse.wheel`](https://playwright.dev/java/docs/api/class-mouse#mouse-wheel) you are now able to scroll vertically or horizontally.
|
By using [`method: Mouse.wheel`] you are now able to scroll vertically or horizontally.
|
||||||
|
|
||||||
### 📜 New Headers API
|
### 📜 New Headers API
|
||||||
|
|
||||||
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
||||||
|
|
||||||
- [Request.allHeaders()](https://playwright.dev/java/docs/api/class-request#request-all-headers)
|
- [`method: Request.allHeaders`]
|
||||||
- [Request.headersArray()](https://playwright.dev/java/docs/api/class-request#request-headers-array)
|
- [`method: Request.headersArray`]
|
||||||
- [Request.headerValue(name: string)](https://playwright.dev/java/docs/api/class-request#request-header-value)
|
- [`method: Request.headerValue`]
|
||||||
- [Response.allHeaders()](https://playwright.dev/java/docs/api/class-response#response-all-headers)
|
- [`method: Response.allHeaders`]
|
||||||
- [Response.headersArray()](https://playwright.dev/java/docs/api/class-response#response-headers-array)
|
- [`method: Response.headersArray`]
|
||||||
- [Response.headerValue(name: string)](https://playwright.dev/java/docs/api/class-response#response-header-value)
|
- [`method: Response.headerValue`]
|
||||||
- [Response.headerValues(name: string)](https://playwright.dev/java/docs/api/class-response#response-header-values)
|
- [`method: Response.headerValues`]
|
||||||
|
|
||||||
### 🌈 Forced-Colors emulation
|
### 🌈 Forced-Colors emulation
|
||||||
|
|
||||||
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [context options](https://playwright.dev/java/docs/api/class-browser#browser-new-context-option-color-scheme) or calling [Page.emulateMedia()](https://playwright.dev/java/docs/api/class-page#page-emulate-media).
|
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [`method: Browser.newContext`] or calling [`method: Page.emulateMedia`].
|
||||||
|
|
||||||
### New APIs
|
### New APIs
|
||||||
|
|
||||||
- [Page.route()](https://playwright.dev/java/docs/api/class-page#page-route) accepts new `times` option to specify how many times this route should be matched.
|
- [`method: Page.route`] accepts new `times` option to specify how many times this route should be matched.
|
||||||
- [Page.setChecked(selector: string, checked: boolean)](https://playwright.dev/java/docs/api/class-page#page-set-checked) and [Locator.setChecked(selector: string, checked: boolean)](https://playwright.dev/java/docs/api/class-locator#locator-set-checked) was introduced to set the checked state of a checkbox.
|
- [`method: Page.setChecked`] and [`method: Locator.setChecked`] were introduced to set the checked state of a checkbox.
|
||||||
- [Request.sizes()](https://playwright.dev/java/docs/api/class-request#request-sizes) Returns resource size information for given http request.
|
- [`method: Request.sizes`] Returns resource size information for given http request.
|
||||||
- [Tracing.startChunk()](https://playwright.dev/java/docs/api/class-tracing#tracing-start-chunk) - Start a new trace chunk.
|
- [`method: Tracing.startChunk`] - Start a new trace chunk.
|
||||||
- [Tracing.stopChunk()](https://playwright.dev/java/docs/api/class-tracing#tracing-stop-chunk) - Stops a new trace chunk.
|
- [`method: Tracing.stopChunk`] - Stops a new trace chunk.
|
||||||
|
|
||||||
### Browser Versions
|
### Browser Versions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,15 @@ import LiteYouTube from '@site/src/components/LiteYouTube';
|
||||||
- New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears:
|
- New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears:
|
||||||
```js
|
```js
|
||||||
// Setup the handler.
|
// Setup the handler.
|
||||||
await page.addLocatorHandler(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
|
await page.addLocatorHandler(
|
||||||
await page.getByRole('button', { name: 'Reject all cookies' }).click();
|
page.getByRole('heading', { name: 'Hej! You are in control of your cookies.' }),
|
||||||
|
async () => {
|
||||||
|
await page.getByRole('button', { name: 'Accept all' }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write the test as usual.
|
// Write the test as usual.
|
||||||
await page.goto('https://example.com');
|
await page.goto('https://www.ikea.com/');
|
||||||
await page.getByRole('button', { name: 'Start here' }).click();
|
await page.getByRole('link', { name: 'Collection of blue and white' }).click();
|
||||||
|
await expect(page.getByRole('heading', { name: 'Light and easy' })).toBeVisible();
|
||||||
```
|
```
|
||||||
|
|
||||||
- `expect(callback).toPass()` timeout can now be configured by `expect.toPass.timeout` option [globally](./api/class-testconfig#test-config-expect) or in [project config](./api/class-testproject#test-project-expect)
|
- `expect(callback).toPass()` timeout can now be configured by `expect.toPass.timeout` option [globally](./api/class-testconfig#test-config-expect) or in [project config](./api/class-testproject#test-project-expect)
|
||||||
|
|
@ -1976,31 +1978,31 @@ This version of Playwright was also tested against the following stable channels
|
||||||
|
|
||||||
#### 🖱️ Mouse Wheel
|
#### 🖱️ Mouse Wheel
|
||||||
|
|
||||||
By using [`Page.mouse.wheel`](https://playwright.dev/docs/api/class-mouse#mouse-wheel) you are now able to scroll vertically or horizontally.
|
By using [`method: Mouse.wheel`] you are now able to scroll vertically or horizontally.
|
||||||
|
|
||||||
#### 📜 New Headers API
|
#### 📜 New Headers API
|
||||||
|
|
||||||
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
||||||
|
|
||||||
- [Request.allHeaders()](https://playwright.dev/docs/api/class-request#request-all-headers)
|
- [`method: Request.allHeaders`]
|
||||||
- [Request.headersArray()](https://playwright.dev/docs/api/class-request#request-headers-array)
|
- [`method: Request.headersArray`]
|
||||||
- [Request.headerValue(name: string)](https://playwright.dev/docs/api/class-request#request-header-value)
|
- [`method: Request.headerValue`]
|
||||||
- [Response.allHeaders()](https://playwright.dev/docs/api/class-response#response-all-headers)
|
- [`method: Response.allHeaders`]
|
||||||
- [Response.headersArray()](https://playwright.dev/docs/api/class-response#response-headers-array)
|
- [`method: Response.headersArray`]
|
||||||
- [Response.headerValue(name: string)](https://playwright.dev/docs/api/class-response#response-header-value)
|
- [`method: Response.headerValue`]
|
||||||
- [Response.headerValues(name: string)](https://playwright.dev/docs/api/class-response#response-header-values)
|
- [`method: Response.headerValues`]
|
||||||
|
|
||||||
#### 🌈 Forced-Colors emulation
|
#### 🌈 Forced-Colors emulation
|
||||||
|
|
||||||
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [context options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-forced-colors) or calling [Page.emulateMedia()](https://playwright.dev/docs/api/class-page#page-emulate-media).
|
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [`method: Browser.newContext`] or calling [`method: Page.emulateMedia`].
|
||||||
|
|
||||||
#### New APIs
|
#### New APIs
|
||||||
|
|
||||||
- [Page.route()](https://playwright.dev/docs/api/class-page#page-route) accepts new `times` option to specify how many times this route should be matched.
|
- [`method: Page.route`] accepts new `times` option to specify how many times this route should be matched.
|
||||||
- [Page.setChecked(selector: string, checked: boolean)](https://playwright.dev/docs/api/class-page#page-set-checked) and [Locator.setChecked(selector: string, checked: boolean)](https://playwright.dev/docs/api/class-locator#locator-set-checked) was introduced to set the checked state of a checkbox.
|
- [`method: Page.setChecked`] and [`method: Locator.setChecked`] were introduced to set the checked state of a checkbox.
|
||||||
- [Request.sizes()](https://playwright.dev/docs/api/class-request#request-sizes) Returns resource size information for given http request.
|
- [`method: Request.sizes`] Returns resource size information for given http request.
|
||||||
- [BrowserContext.tracing.startChunk()](https://playwright.dev/docs/api/class-tracing#tracing-start-chunk) - Start a new trace chunk.
|
- [`method: Tracing.startChunk`] - Start a new trace chunk.
|
||||||
- [BrowserContext.tracing.stopChunk()](https://playwright.dev/docs/api/class-tracing#tracing-stop-chunk) - Stops a new trace chunk.
|
- [`method: Tracing.stopChunk`] - Stops a new trace chunk.
|
||||||
|
|
||||||
### 🎭 Playwright Test
|
### 🎭 Playwright Test
|
||||||
|
|
||||||
|
|
@ -2015,11 +2017,11 @@ test.describe.parallel('group', () => {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, tests in a single file are run in order. If you have many independent tests in a single file, you can now run them in parallel with [test.describe.parallel(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-parallel).
|
By default, tests in a single file are run in order. If you have many independent tests in a single file, you can now run them in parallel with [test.describe.parallel(title, callback)](./api/class-test#test-describe-parallel).
|
||||||
|
|
||||||
#### 🛠 Add `--debug` CLI flag
|
#### 🛠 Add `--debug` CLI flag
|
||||||
|
|
||||||
By using `npx playwright test --debug` it will enable the [Playwright Inspector](https://playwright.dev/docs/debug#playwright-inspector) for you to debug your tests.
|
By using `npx playwright test --debug` it will enable the [Playwright Inspector](./debug#playwright-inspector) for you to debug your tests.
|
||||||
|
|
||||||
### Browser Versions
|
### Browser Versions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,43 @@ title: "Release notes"
|
||||||
toc_max_heading_level: 2
|
toc_max_heading_level: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Version 1.42
|
||||||
|
|
||||||
|
### New Locator Handler
|
||||||
|
|
||||||
|
New method [`method: Page.addLocatorHandler`] registers a callback that will be invoked when specified element becomes visible and may block Playwright actions. The callback can get rid of the overlay. Here is an example that closes a cookie dialog when it appears.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Setup the handler.
|
||||||
|
page.add_locator_handler(
|
||||||
|
page.get_by_role("heading", name="Hej! You are in control of your cookies."),
|
||||||
|
lambda: page.get_by_role("button", name="Accept all").click(),
|
||||||
|
)
|
||||||
|
# Write the test as usual.
|
||||||
|
page.goto("https://www.ikea.com/")
|
||||||
|
page.get_by_role("link", name="Collection of blue and white").click()
|
||||||
|
expect(page.get_by_role("heading", name="Light and easy")).to_be_visible()
|
||||||
|
```
|
||||||
|
|
||||||
|
### New APIs
|
||||||
|
|
||||||
|
- [`method: Page.pdf`] accepts two new options [`option: tagged`] and [`option: outline`].
|
||||||
|
|
||||||
|
### Announcements
|
||||||
|
|
||||||
|
* ⚠️ Ubuntu 18 is not supported anymore.
|
||||||
|
|
||||||
|
### Browser Versions
|
||||||
|
|
||||||
|
* Chromium 121.0.6167.57
|
||||||
|
* Mozilla Firefox 121.0
|
||||||
|
* WebKit 17.4
|
||||||
|
|
||||||
|
This version was also tested against the following stable channels:
|
||||||
|
|
||||||
|
* Google Chrome 120
|
||||||
|
* Microsoft Edge 120
|
||||||
|
|
||||||
## Version 1.41
|
## Version 1.41
|
||||||
|
|
||||||
### New APIs
|
### New APIs
|
||||||
|
|
@ -1003,31 +1040,31 @@ This version of Playwright was also tested against the following stable channels
|
||||||
|
|
||||||
### 🖱️ Mouse Wheel
|
### 🖱️ Mouse Wheel
|
||||||
|
|
||||||
By using [`Page.mouse.wheel`](https://playwright.dev/python/docs/api/class-mouse#mouse-wheel) you are now able to scroll vertically or horizontally.
|
By using [`method: Mouse.wheel`] you are now able to scroll vertically or horizontally.
|
||||||
|
|
||||||
### 📜 New Headers API
|
### 📜 New Headers API
|
||||||
|
|
||||||
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
Previously it was not possible to get multiple header values of a response. This is now possible and additional helper functions are available:
|
||||||
|
|
||||||
- [Request.all_headers()](https://playwright.dev/python/docs/api/class-request#request-all-headers)
|
- [`method: Request.allHeaders`]
|
||||||
- [Request.headers_array()](https://playwright.dev/python/docs/api/class-request#request-headers-array)
|
- [`method: Request.headersArray`]
|
||||||
- [Request.header_value(name: str)](https://playwright.dev/python/docs/api/class-request#request-header-value)
|
- [`method: Request.headerValue`]
|
||||||
- [Response.all_headers()](https://playwright.dev/python/docs/api/class-response#response-all-headers)
|
- [`method: Response.allHeaders`]
|
||||||
- [Response.headers_array()](https://playwright.dev/python/docs/api/class-response#response-headers-array)
|
- [`method: Response.headersArray`]
|
||||||
- [Response.header_value(name: str)](https://playwright.dev/python/docs/api/class-response#response-header-value)
|
- [`method: Response.headerValue`]
|
||||||
- [Response.header_values(name: str)](https://playwright.dev/python/docs/api/class-response#response-header-values)
|
- [`method: Response.headerValues`]
|
||||||
|
|
||||||
### 🌈 Forced-Colors emulation
|
### 🌈 Forced-Colors emulation
|
||||||
|
|
||||||
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [context options](https://playwright.dev/python/docs/api/class-browser#browser-new-context-option-forced-colors) or calling [Page.emulate_media()](https://playwright.dev/python/docs/api/class-page#page-emulate-media).
|
Its now possible to emulate the `forced-colors` CSS media feature by passing it in the [`method: Browser.newContext`] or calling [`method: Page.emulateMedia`].
|
||||||
|
|
||||||
### New APIs
|
### New APIs
|
||||||
|
|
||||||
- [Page.route()](https://playwright.dev/python/docs/api/class-page#page-route) accepts new `times` option to specify how many times this route should be matched.
|
- [`method: Page.route`] accepts new `times` option to specify how many times this route should be matched.
|
||||||
- [Page.set_checked(selector: str, checked: bool)](https://playwright.dev/python/docs/api/class-page#page-set-checked) and [Locator.set_checked(selector: str, checked: bool)](https://playwright.dev/python/docs/api/class-locator#locator-set-checked) was introduced to set the checked state of a checkbox.
|
- [`method: Page.setChecked`] and [`method: Locator.setChecked`] were introduced to set the checked state of a checkbox.
|
||||||
- [Request.sizes()](https://playwright.dev/python/docs/api/class-request#request-sizes) Returns resource size information for given http request.
|
- [`method: Request.sizes`] Returns resource size information for given http request.
|
||||||
- [BrowserContext.tracing.start_chunk()](https://playwright.dev/python/docs/api/class-tracing#tracing-start-chunk) - Start a new trace chunk.
|
- [`method: Tracing.startChunk`] - Start a new trace chunk.
|
||||||
- [BrowserContext.tracing.stop_chunk()](https://playwright.dev/python/docs/api/class-tracing#tracing-stop-chunk) - Stops a new trace chunk.
|
- [`method: Tracing.stopChunk`] - Stops a new trace chunk.
|
||||||
|
|
||||||
### Browser Versions
|
### Browser Versions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ public class TestExample {
|
||||||
|
|
||||||
See [here](./test-runners.md) for further details on how to run tests in parallel, etc.
|
See [here](./test-runners.md) for further details on how to run tests in parallel, etc.
|
||||||
|
|
||||||
|
See experimental [JUnit integration](./junit.md) to automatically initialize Playwright objects and more.
|
||||||
|
|
||||||
## What's Next
|
## What's Next
|
||||||
|
|
||||||
- [Debugging tests](./debug.md)
|
- [Debugging tests](./debug.md)
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,12 @@ self.addEventListener('fetch', event => {
|
||||||
(async () => {
|
(async () => {
|
||||||
// 1. Try to first serve directly from caches
|
// 1. Try to first serve directly from caches
|
||||||
const response = await caches.match(event.request);
|
const response = await caches.match(event.request);
|
||||||
if (response) return response;
|
if (response)
|
||||||
|
return response;
|
||||||
|
|
||||||
// 2. Re-write request for /foo to /bar
|
// 2. Re-write request for /foo to /bar
|
||||||
if (event.request.url.endsWith('foo')) return fetch('./bar');
|
if (event.request.url.endsWith('foo'))
|
||||||
|
return fetch('./bar');
|
||||||
|
|
||||||
// 3. Prevent tracker.js from being retrieved, and returns a placeholder response
|
// 3. Prevent tracker.js from being retrieved, and returns a placeholder response
|
||||||
if (event.request.url.endsWith('tracker.js')) {
|
if (event.request.url.endsWith('tracker.js')) {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,8 @@ public class TestExample {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See experimental [JUnit integration](./junit.md) to automatically initialize Playwright objects and more.
|
||||||
|
|
||||||
### Running Tests in Parallel
|
### Running Tests in Parallel
|
||||||
|
|
||||||
By default JUnit will run all tests sequentially on a single thread. Since JUnit 5.3 you can change this behavior to run tests in parallel
|
By default JUnit will run all tests sequentially on a single thread. Since JUnit 5.3 you can change this behavior to run tests in parallel
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,14 @@ def test_my_app_is_working(fixture_name):
|
||||||
|
|
||||||
**Function scope**: These fixtures are created when requested in a test function and destroyed when the test ends.
|
**Function scope**: These fixtures are created when requested in a test function and destroyed when the test ends.
|
||||||
|
|
||||||
- `context`: New [browser context](https://playwright.dev/python/docs/browser-contexts) for a test.
|
- `context`: New [browser context](./browser-contexts) for a test.
|
||||||
- `page`: New [browser page](https://playwright.dev/python/docs/pages) for a test.
|
- `page`: New [browser page](./pages) for a test.
|
||||||
|
|
||||||
**Session scope**: These fixtures are created when requested in a test function and destroyed when all tests end.
|
**Session scope**: These fixtures are created when requested in a test function and destroyed when all tests end.
|
||||||
|
|
||||||
- `playwright`: [Playwright](https://playwright.dev/python/docs/api/class-playwright) instance.
|
- `playwright`: [Playwright](./api/class-playwright) instance.
|
||||||
- `browser_type`: [BrowserType](https://playwright.dev/python/docs/api/class-browsertype) instance of the current browser.
|
- `browser_type`: [BrowserType](./api/class-browsertype) instance of the current browser.
|
||||||
- `browser`: [Browser](https://playwright.dev/python/docs/api/class-browser) instance launched by Playwright.
|
- `browser`: [Browser](./api/class-browser) instance launched by Playwright.
|
||||||
- `browser_name`: Browser name as string.
|
- `browser_name`: Browser name as string.
|
||||||
- `browser_channel`: Browser channel as string.
|
- `browser_channel`: Browser channel as string.
|
||||||
- `is_chromium`, `is_webkit`, `is_firefox`: Booleans for the respective browser types.
|
- `is_chromium`, `is_webkit`, `is_firefox`: Booleans for the respective browser types.
|
||||||
|
|
|
||||||
86
package-lock.json
generated
86
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-internal",
|
"name": "playwright-internal",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "playwright-internal",
|
"name": "playwright-internal",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
@ -8136,10 +8136,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright": {
|
"packages/playwright": {
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -8153,11 +8153,11 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-browser-chromium": {
|
"packages/playwright-browser-chromium": {
|
||||||
"name": "@playwright/browser-chromium",
|
"name": "@playwright/browser-chromium",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
@ -8165,11 +8165,11 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-browser-firefox": {
|
"packages/playwright-browser-firefox": {
|
||||||
"name": "@playwright/browser-firefox",
|
"name": "@playwright/browser-firefox",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
@ -8177,22 +8177,22 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-browser-webkit": {
|
"packages/playwright-browser-webkit": {
|
||||||
"name": "@playwright/browser-webkit",
|
"name": "@playwright/browser-webkit",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-chromium": {
|
"packages/playwright-chromium": {
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -8202,7 +8202,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-core": {
|
"packages/playwright-core": {
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
|
|
@ -8213,11 +8213,11 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-core": {
|
"packages/playwright-ct-core": {
|
||||||
"name": "@playwright/experimental-ct-core",
|
"name": "@playwright/experimental-ct-core",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.42.0-next",
|
"playwright": "1.42.1",
|
||||||
"playwright-core": "1.42.0-next",
|
"playwright-core": "1.42.1",
|
||||||
"vite": "^5.0.12"
|
"vite": "^5.0.12"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -8229,15 +8229,14 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-react": {
|
"packages/playwright-ct-react": {
|
||||||
"name": "@playwright/experimental-ct-react",
|
"name": "@playwright/experimental-ct-react",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-react": "^4.2.1"
|
"@vitejs/plugin-react": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-react": "cli.js"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
@ -8245,15 +8244,14 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-react17": {
|
"packages/playwright-ct-react17": {
|
||||||
"name": "@playwright/experimental-ct-react17",
|
"name": "@playwright/experimental-ct-react17",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-react": "^4.2.1"
|
"@vitejs/plugin-react": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-react17": "cli.js"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
@ -8261,15 +8259,14 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-solid": {
|
"packages/playwright-ct-solid": {
|
||||||
"name": "@playwright/experimental-ct-solid",
|
"name": "@playwright/experimental-ct-solid",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"vite-plugin-solid": "^2.7.0"
|
"vite-plugin-solid": "^2.7.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-solid": "cli.js"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"solid-js": "^1.7.0"
|
"solid-js": "^1.7.0"
|
||||||
|
|
@ -8280,15 +8277,14 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-svelte": {
|
"packages/playwright-ct-svelte": {
|
||||||
"name": "@playwright/experimental-ct-svelte",
|
"name": "@playwright/experimental-ct-svelte",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-svelte": "cli.js"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"svelte": "^4.2.8"
|
"svelte": "^4.2.8"
|
||||||
|
|
@ -8299,15 +8295,14 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-vue": {
|
"packages/playwright-ct-vue": {
|
||||||
"name": "@playwright/experimental-ct-vue",
|
"name": "@playwright/experimental-ct-vue",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-vue": "^4.2.1"
|
"@vitejs/plugin-vue": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-vue": "cli.js"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
|
@ -8315,15 +8310,14 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-ct-vue2": {
|
"packages/playwright-ct-vue2": {
|
||||||
"name": "@playwright/experimental-ct-vue2",
|
"name": "@playwright/experimental-ct-vue2",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-vue2": "^2.2.0"
|
"@vitejs/plugin-vue2": "^2.2.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-vue2": "cli.js"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vue": "^2.7.14"
|
"vue": "^2.7.14"
|
||||||
|
|
@ -8368,11 +8362,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-firefox": {
|
"packages/playwright-firefox": {
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -8383,10 +8377,10 @@
|
||||||
},
|
},
|
||||||
"packages/playwright-test": {
|
"packages/playwright-test": {
|
||||||
"name": "@playwright/test",
|
"name": "@playwright/test",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.42.0-next"
|
"playwright": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
@ -8396,11 +8390,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/playwright-webkit": {
|
"packages/playwright-webkit": {
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-internal",
|
"name": "playwright-internal",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ".eslintrc.js",
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-base-to-string": "error",
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
project: "./tsconfig.json"
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/browser-chromium",
|
"name": "@playwright/browser-chromium",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright package that automatically installs Chromium",
|
"description": "Playwright package that automatically installs Chromium",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/browser-firefox",
|
"name": "@playwright/browser-firefox",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright package that automatically installs Firefox",
|
"description": "Playwright package that automatically installs Firefox",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/browser-webkit",
|
"name": "@playwright/browser-webkit",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright package that automatically installs WebKit",
|
"description": "Playwright package that automatically installs WebKit",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,5 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('playwright-core/lib/program');
|
const { program } = require('playwright-core/lib/cli/program');
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-chromium",
|
"name": "playwright-chromium",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "A high-level API to automate Chromium",
|
"description": "A high-level API to automate Chromium",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: "../.eslintrc-with-ts-config.js",
|
extends: "../../.eslintrc-with-ts-config.js",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-core",
|
"name": "playwright-core",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
||||||
|
|
@ -526,7 +526,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
|
||||||
function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
|
function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
|
||||||
if (acceptDownloads === undefined)
|
if (acceptDownloads === undefined)
|
||||||
return undefined;
|
return undefined;
|
||||||
if (acceptDownloads === true)
|
if (acceptDownloads)
|
||||||
return 'accept';
|
return 'accept';
|
||||||
return 'deny';
|
return 'deny';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -594,7 +594,8 @@ export class CRBrowserContext extends BrowserContext {
|
||||||
targetId = (page._delegate as CRPage)._targetId;
|
targetId = (page._delegate as CRPage)._targetId;
|
||||||
} else if (page instanceof Frame) {
|
} else if (page instanceof Frame) {
|
||||||
const session = (page._page._delegate as CRPage)._sessions.get(page._id);
|
const session = (page._page._delegate as CRPage)._sessions.get(page._id);
|
||||||
if (!session) throw new Error(`This frame does not have a separate CDP session, it is a part of the parent frame's session`);
|
if (!session)
|
||||||
|
throw new Error(`This frame does not have a separate CDP session, it is a part of the parent frame's session`);
|
||||||
targetId = session._targetId;
|
targetId = session._targetId;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('page: expected Page or Frame');
|
throw new Error('page: expected Page or Frame');
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,9 @@
|
||||||
|
|
||||||
import type { SelectorEngine, SelectorRoot } from './selectorEngine';
|
import type { SelectorEngine, SelectorRoot } from './selectorEngine';
|
||||||
import { matchesAttributePart } from './selectorUtils';
|
import { matchesAttributePart } from './selectorUtils';
|
||||||
import { beginAriaCaches, endAriaCaches, getAriaChecked, getAriaDisabled, getAriaExpanded, getAriaLevel, getAriaPressed, getAriaSelected, getElementAccessibleName, getElementsByRole, isElementHiddenForAria, kAriaCheckedRoles, kAriaExpandedRoles, kAriaLevelRoles, kAriaPressedRoles, kAriaSelectedRoles } from './roleUtils';
|
import { beginAriaCaches, endAriaCaches, getAriaChecked, getAriaDisabled, getAriaExpanded, getAriaLevel, getAriaPressed, getAriaRole, getAriaSelected, getElementAccessibleName, isElementHiddenForAria, kAriaCheckedRoles, kAriaExpandedRoles, kAriaLevelRoles, kAriaPressedRoles, kAriaSelectedRoles } from './roleUtils';
|
||||||
import { parseAttributeSelector, type AttributeSelectorPart, type AttributeSelectorOperator } from '../../utils/isomorphic/selectorParser';
|
import { parseAttributeSelector, type AttributeSelectorPart, type AttributeSelectorOperator } from '../../utils/isomorphic/selectorParser';
|
||||||
import { normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils';
|
import { normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils';
|
||||||
import { isInsideScope } from './domUtils';
|
|
||||||
|
|
||||||
type RoleEngineOptions = {
|
type RoleEngineOptions = {
|
||||||
role: string;
|
role: string;
|
||||||
|
|
@ -126,27 +125,26 @@ function validateAttributes(attrs: AttributeSelectorPart[], role: string): RoleE
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: boolean): Element[] {
|
function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: boolean): Element[] {
|
||||||
const doc = scope.nodeType === 9 /* Node.DOCUMENT_NODE */ ? scope as Document : scope.ownerDocument;
|
const result: Element[] = [];
|
||||||
const elements = doc ? getElementsByRole(doc, options.role) : [];
|
const match = (element: Element) => {
|
||||||
return elements.filter(element => {
|
if (getAriaRole(element) !== options.role)
|
||||||
if (!isInsideScope(scope, element))
|
return;
|
||||||
return false;
|
|
||||||
if (options.selected !== undefined && getAriaSelected(element) !== options.selected)
|
if (options.selected !== undefined && getAriaSelected(element) !== options.selected)
|
||||||
return false;
|
return;
|
||||||
if (options.checked !== undefined && getAriaChecked(element) !== options.checked)
|
if (options.checked !== undefined && getAriaChecked(element) !== options.checked)
|
||||||
return false;
|
return;
|
||||||
if (options.pressed !== undefined && getAriaPressed(element) !== options.pressed)
|
if (options.pressed !== undefined && getAriaPressed(element) !== options.pressed)
|
||||||
return false;
|
return;
|
||||||
if (options.expanded !== undefined && getAriaExpanded(element) !== options.expanded)
|
if (options.expanded !== undefined && getAriaExpanded(element) !== options.expanded)
|
||||||
return false;
|
return;
|
||||||
if (options.level !== undefined && getAriaLevel(element) !== options.level)
|
if (options.level !== undefined && getAriaLevel(element) !== options.level)
|
||||||
return false;
|
return;
|
||||||
if (options.disabled !== undefined && getAriaDisabled(element) !== options.disabled)
|
if (options.disabled !== undefined && getAriaDisabled(element) !== options.disabled)
|
||||||
return false;
|
return;
|
||||||
if (!options.includeHidden) {
|
if (!options.includeHidden) {
|
||||||
const isHidden = isElementHiddenForAria(element);
|
const isHidden = isElementHiddenForAria(element);
|
||||||
if (isHidden)
|
if (isHidden)
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
if (options.name !== undefined) {
|
if (options.name !== undefined) {
|
||||||
// Always normalize whitespace in the accessible name.
|
// Always normalize whitespace in the accessible name.
|
||||||
|
|
@ -157,10 +155,25 @@ function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: bo
|
||||||
if (internal && !options.exact && options.nameOp === '=')
|
if (internal && !options.exact && options.nameOp === '=')
|
||||||
options.nameOp = '*=';
|
options.nameOp = '*=';
|
||||||
if (!matchesAttributePart(accessibleName, { name: '', jsonPath: [], op: options.nameOp || '=', value: options.name, caseSensitive: !!options.exact }))
|
if (!matchesAttributePart(accessibleName, { name: '', jsonPath: [], op: options.nameOp || '=', value: options.name, caseSensitive: !!options.exact }))
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
return true;
|
result.push(element);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const query = (root: Element | ShadowRoot | Document) => {
|
||||||
|
const shadows: ShadowRoot[] = [];
|
||||||
|
if ((root as Element).shadowRoot)
|
||||||
|
shadows.push((root as Element).shadowRoot!);
|
||||||
|
for (const element of root.querySelectorAll('*')) {
|
||||||
|
match(element);
|
||||||
|
if (element.shadowRoot)
|
||||||
|
shadows.push(element.shadowRoot);
|
||||||
|
}
|
||||||
|
shadows.forEach(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
query(scope);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRoleEngine(internal: boolean): SelectorEngine {
|
export function createRoleEngine(internal: boolean): SelectorEngine {
|
||||||
|
|
|
||||||
|
|
@ -845,51 +845,11 @@ function getAccessibleNameFromAssociatedLabels(labels: Iterable<HTMLLabelElement
|
||||||
})).filter(accessibleName => !!accessibleName).join(' ');
|
})).filter(accessibleName => !!accessibleName).join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getElementsByRole(document: Document, role: string): Element[] {
|
|
||||||
if (document === cacheElementsByRoleDocument)
|
|
||||||
return cacheElementsByRole!.get(role) || [];
|
|
||||||
const map = calculateElementsByRoleMap(document);
|
|
||||||
if (cachesCounter) {
|
|
||||||
cacheElementsByRoleDocument = document;
|
|
||||||
cacheElementsByRole = map;
|
|
||||||
}
|
|
||||||
return map.get(role) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateElementsByRoleMap(document: Document) {
|
|
||||||
const result = new Map<string, Element[]>();
|
|
||||||
|
|
||||||
const visit = (root: Element | ShadowRoot | Document) => {
|
|
||||||
const shadows: ShadowRoot[] = [];
|
|
||||||
if ((root as Element).shadowRoot)
|
|
||||||
shadows.push((root as Element).shadowRoot!);
|
|
||||||
for (const element of root.querySelectorAll('*')) {
|
|
||||||
const role = getAriaRole(element);
|
|
||||||
if (role) {
|
|
||||||
let list = result.get(role);
|
|
||||||
if (!list) {
|
|
||||||
list = [];
|
|
||||||
result.set(role, list);
|
|
||||||
}
|
|
||||||
list.push(element);
|
|
||||||
}
|
|
||||||
if (element.shadowRoot)
|
|
||||||
shadows.push(element.shadowRoot);
|
|
||||||
}
|
|
||||||
shadows.forEach(visit);
|
|
||||||
};
|
|
||||||
visit(document);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cacheAccessibleName: Map<Element, string> | undefined;
|
let cacheAccessibleName: Map<Element, string> | undefined;
|
||||||
let cacheAccessibleNameHidden: Map<Element, string> | undefined;
|
let cacheAccessibleNameHidden: Map<Element, string> | undefined;
|
||||||
let cacheIsHidden: Map<Element, boolean> | undefined;
|
let cacheIsHidden: Map<Element, boolean> | undefined;
|
||||||
let cachePseudoContentBefore: Map<Element, string> | undefined;
|
let cachePseudoContentBefore: Map<Element, string> | undefined;
|
||||||
let cachePseudoContentAfter: Map<Element, string> | undefined;
|
let cachePseudoContentAfter: Map<Element, string> | undefined;
|
||||||
let cacheElementsByRole: Map<string, Element[]> | undefined;
|
|
||||||
let cacheElementsByRoleDocument: Document | undefined;
|
|
||||||
let cachesCounter = 0;
|
let cachesCounter = 0;
|
||||||
|
|
||||||
export function beginAriaCaches() {
|
export function beginAriaCaches() {
|
||||||
|
|
@ -908,7 +868,5 @@ export function endAriaCaches() {
|
||||||
cacheIsHidden = undefined;
|
cacheIsHidden = undefined;
|
||||||
cachePseudoContentBefore = undefined;
|
cachePseudoContentBefore = undefined;
|
||||||
cachePseudoContentAfter = undefined;
|
cachePseudoContentAfter = undefined;
|
||||||
cacheElementsByRole = undefined;
|
|
||||||
cacheElementsByRoleDocument = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,12 +86,18 @@ function buildComponentsTreeVue3(instance: VueVNode): ComponentNode {
|
||||||
// @see https://github.com/vuejs/devtools/blob/e7132f3392b975e39e1d9a23cf30456c270099c2/packages/app-backend-vue3/src/components/util.ts#L29
|
// @see https://github.com/vuejs/devtools/blob/e7132f3392b975e39e1d9a23cf30456c270099c2/packages/app-backend-vue3/src/components/util.ts#L29
|
||||||
function getInstanceName(instance: VueVNode): string {
|
function getInstanceName(instance: VueVNode): string {
|
||||||
const name = getComponentTypeName(instance.type || {});
|
const name = getComponentTypeName(instance.type || {});
|
||||||
if (name) return name;
|
if (name)
|
||||||
if (instance.root === instance) return 'Root';
|
return name;
|
||||||
for (const key in instance.parent?.type?.components)
|
if (instance.root === instance)
|
||||||
if (instance.parent?.type.components[key] === instance.type) return saveComponentName(instance, key);
|
return 'Root';
|
||||||
for (const key in instance.appContext?.components)
|
for (const key in instance.parent?.type?.components) {
|
||||||
if (instance.appContext.components[key] === instance.type) return saveComponentName(instance, key);
|
if (instance.parent?.type.components[key] === instance.type)
|
||||||
|
return saveComponentName(instance, key);
|
||||||
|
}
|
||||||
|
for (const key in instance.appContext?.components) {
|
||||||
|
if (instance.appContext.components[key] === instance.type)
|
||||||
|
return saveComponentName(instance, key);
|
||||||
|
}
|
||||||
return 'Anonymous Component';
|
return 'Anonymous Component';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +138,8 @@ function buildComponentsTreeVue3(instance: VueVNode): ComponentNode {
|
||||||
|
|
||||||
// @see https://github.com/vuejs/devtools/blob/e7132f3392b975e39e1d9a23cf30456c270099c2/packages/app-backend-vue3/src/components/el.ts#L15
|
// @see https://github.com/vuejs/devtools/blob/e7132f3392b975e39e1d9a23cf30456c270099c2/packages/app-backend-vue3/src/components/el.ts#L15
|
||||||
function getFragmentRootElements(vnode: any): Element[] {
|
function getFragmentRootElements(vnode: any): Element[] {
|
||||||
if (!vnode.children) return [];
|
if (!vnode.children)
|
||||||
|
return [];
|
||||||
|
|
||||||
const list = [];
|
const list = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,10 @@ function preprocess(str: string): number[] {
|
||||||
if (code === 0xd && str.charCodeAt(i + 1) === 0xa) {
|
if (code === 0xd && str.charCodeAt(i + 1) === 0xa) {
|
||||||
code = 0xa; i++;
|
code = 0xa; i++;
|
||||||
}
|
}
|
||||||
if (code === 0xd || code === 0xc) code = 0xa;
|
if (code === 0xd || code === 0xc)
|
||||||
if (code === 0x0) code = 0xfffd;
|
code = 0xa;
|
||||||
|
if (code === 0x0)
|
||||||
|
code = 0xfffd;
|
||||||
if (between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i + 1), 0xdc00, 0xdfff)) {
|
if (between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i + 1), 0xdc00, 0xdfff)) {
|
||||||
// Decode a surrogate pair into an astral codepoint.
|
// Decode a surrogate pair into an astral codepoint.
|
||||||
const lead = code - 0xd800;
|
const lead = code - 0xd800;
|
||||||
|
|
@ -63,7 +65,8 @@ function preprocess(str: string): number[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringFromCode(code: number) {
|
function stringFromCode(code: number) {
|
||||||
if (code <= 0xffff) return String.fromCharCode(code);
|
if (code <= 0xffff)
|
||||||
|
return String.fromCharCode(code);
|
||||||
// Otherwise, encode astral char as surrogate pair.
|
// Otherwise, encode astral char as surrogate pair.
|
||||||
code -= Math.pow(2, 16);
|
code -= Math.pow(2, 16);
|
||||||
const lead = Math.floor(code / Math.pow(2, 10)) + 0xd800;
|
const lead = Math.floor(code / Math.pow(2, 10)) + 0xd800;
|
||||||
|
|
@ -107,8 +110,10 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
num = 1;
|
num = 1;
|
||||||
i += num;
|
i += num;
|
||||||
code = codepoint(i);
|
code = codepoint(i);
|
||||||
if (newline(code)) incrLineno();
|
if (newline(code))
|
||||||
else column += num;
|
incrLineno();
|
||||||
|
else
|
||||||
|
column += num;
|
||||||
// console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
|
// console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
@ -125,7 +130,8 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
const eof = function(codepoint?: number): boolean {
|
const eof = function(codepoint?: number): boolean {
|
||||||
if (codepoint === undefined) codepoint = code;
|
if (codepoint === undefined)
|
||||||
|
codepoint = code;
|
||||||
return codepoint === -1;
|
return codepoint === -1;
|
||||||
};
|
};
|
||||||
const donothing = function() { };
|
const donothing = function() { };
|
||||||
|
|
@ -138,12 +144,14 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
consumeComments();
|
consumeComments();
|
||||||
consume();
|
consume();
|
||||||
if (whitespace(code)) {
|
if (whitespace(code)) {
|
||||||
while (whitespace(next())) consume();
|
while (whitespace(next()))
|
||||||
|
consume();
|
||||||
return new WhitespaceToken();
|
return new WhitespaceToken();
|
||||||
} else if (code === 0x22) {return consumeAStringToken();} else if (code === 0x23) {
|
} else if (code === 0x22) {return consumeAStringToken();} else if (code === 0x23) {
|
||||||
if (namechar(next()) || areAValidEscape(next(1), next(2))) {
|
if (namechar(next()) || areAValidEscape(next(1), next(2))) {
|
||||||
const token = new HashToken('');
|
const token = new HashToken('');
|
||||||
if (wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = 'id';
|
if (wouldStartAnIdentifier(next(1), next(2), next(3)))
|
||||||
|
token.type = 'id';
|
||||||
token.value = consumeAName();
|
token.value = consumeAName();
|
||||||
return token;
|
return token;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -288,7 +296,8 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
const str = consumeAName();
|
const str = consumeAName();
|
||||||
if (str.toLowerCase() === 'url' && next() === 0x28) {
|
if (str.toLowerCase() === 'url' && next() === 0x28) {
|
||||||
consume();
|
consume();
|
||||||
while (whitespace(next(1)) && whitespace(next(2))) consume();
|
while (whitespace(next(1)) && whitespace(next(2)))
|
||||||
|
consume();
|
||||||
if (next() === 0x22 || next() === 0x27)
|
if (next() === 0x22 || next() === 0x27)
|
||||||
return new FunctionToken(str);
|
return new FunctionToken(str);
|
||||||
else if (whitespace(next()) && (next(2) === 0x22 || next(2) === 0x27))
|
else if (whitespace(next()) && (next(2) === 0x22 || next(2) === 0x27))
|
||||||
|
|
@ -305,7 +314,8 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
};
|
};
|
||||||
|
|
||||||
const consumeAStringToken = function(endingCodePoint?: number): CSSParserToken {
|
const consumeAStringToken = function(endingCodePoint?: number): CSSParserToken {
|
||||||
if (endingCodePoint === undefined) endingCodePoint = code;
|
if (endingCodePoint === undefined)
|
||||||
|
endingCodePoint = code;
|
||||||
let string = '';
|
let string = '';
|
||||||
while (consume()) {
|
while (consume()) {
|
||||||
if (code === endingCodePoint || eof()) {
|
if (code === endingCodePoint || eof()) {
|
||||||
|
|
@ -331,13 +341,16 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
|
|
||||||
const consumeAURLToken = function(): CSSTokenInterface {
|
const consumeAURLToken = function(): CSSTokenInterface {
|
||||||
const token = new URLToken('');
|
const token = new URLToken('');
|
||||||
while (whitespace(next())) consume();
|
while (whitespace(next()))
|
||||||
if (eof(next())) return token;
|
consume();
|
||||||
|
if (eof(next()))
|
||||||
|
return token;
|
||||||
while (consume()) {
|
while (consume()) {
|
||||||
if (code === 0x29 || eof()) {
|
if (code === 0x29 || eof()) {
|
||||||
return token;
|
return token;
|
||||||
} else if (whitespace(code)) {
|
} else if (whitespace(code)) {
|
||||||
while (whitespace(next())) consume();
|
while (whitespace(next()))
|
||||||
|
consume();
|
||||||
if (next() === 0x29 || eof(next())) {
|
if (next() === 0x29 || eof(next())) {
|
||||||
consume();
|
consume();
|
||||||
return token;
|
return token;
|
||||||
|
|
@ -379,9 +392,11 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (whitespace(next())) consume();
|
if (whitespace(next()))
|
||||||
|
consume();
|
||||||
let value = parseInt(digits.map(function(x) { return String.fromCharCode(x); }).join(''), 16);
|
let value = parseInt(digits.map(function(x) { return String.fromCharCode(x); }).join(''), 16);
|
||||||
if (value > maximumallowedcodepoint) value = 0xfffd;
|
if (value > maximumallowedcodepoint)
|
||||||
|
value = 0xfffd;
|
||||||
return value;
|
return value;
|
||||||
} else if (eof()) {
|
} else if (eof()) {
|
||||||
return 0xfffd;
|
return 0xfffd;
|
||||||
|
|
@ -391,8 +406,10 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
};
|
};
|
||||||
|
|
||||||
const areAValidEscape = function(c1: number, c2: number) {
|
const areAValidEscape = function(c1: number, c2: number) {
|
||||||
if (c1 !== 0x5c) return false;
|
if (c1 !== 0x5c)
|
||||||
if (newline(c2)) return false;
|
return false;
|
||||||
|
if (newline(c2))
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
const startsWithAValidEscape = function() {
|
const startsWithAValidEscape = function() {
|
||||||
|
|
@ -416,11 +433,14 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
|
|
||||||
const wouldStartANumber = function(c1: number, c2: number, c3: number) {
|
const wouldStartANumber = function(c1: number, c2: number, c3: number) {
|
||||||
if (c1 === 0x2b || c1 === 0x2d) {
|
if (c1 === 0x2b || c1 === 0x2d) {
|
||||||
if (digit(c2)) return true;
|
if (digit(c2))
|
||||||
if (c2 === 0x2e && digit(c3)) return true;
|
return true;
|
||||||
|
if (c2 === 0x2e && digit(c3))
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
} else if (c1 === 0x2e) {
|
} else if (c1 === 0x2e) {
|
||||||
if (digit(c2)) return true;
|
if (digit(c2))
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
} else if (digit(c1)) {
|
} else if (digit(c1)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -519,7 +539,8 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
||||||
while (!eof(next())) {
|
while (!eof(next())) {
|
||||||
tokens.push(consumeAToken());
|
tokens.push(consumeAToken());
|
||||||
iterationCount++;
|
iterationCount++;
|
||||||
if (iterationCount > str.length * 2) throw new Error("I'm infinite-looping!");
|
if (iterationCount > str.length * 2)
|
||||||
|
throw new Error("I'm infinite-looping!");
|
||||||
}
|
}
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,9 +181,8 @@ export async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onLog
|
||||||
|
|
||||||
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onLog?: (data: string) => void, onStdErr?: (data: string) => void): Promise<number> {
|
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onLog?: (data: string) => void, onStdErr?: (data: string) => void): Promise<number> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
onLog?.(`HTTP HEAD: ${url}`);
|
onLog?.(`HTTP GET: ${url}`);
|
||||||
httpRequest({
|
httpRequest({
|
||||||
method: 'HEAD',
|
|
||||||
url: url.toString(),
|
url: url.toString(),
|
||||||
headers: { Accept: '*/*' },
|
headers: { Accept: '*/*' },
|
||||||
rejectUnauthorized: !ignoreHTTPSErrors
|
rejectUnauthorized: !ignoreHTTPSErrors
|
||||||
|
|
|
||||||
49
packages/playwright-core/types/types.d.ts
vendored
49
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -1781,26 +1781,30 @@ export interface Page {
|
||||||
prependListener(event: 'worker', listener: (worker: Worker) => void): this;
|
prependListener(event: 'worker', listener: (worker: Worker) => void): this;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sometimes, the web page can show an overlay that obstructs elements behind it and prevents certain actions, like
|
* **NOTE** This method is experimental and its behavior may change in the upcoming releases.
|
||||||
* click, from completing. When such an overlay is shown predictably, we recommend dismissing it as a part of your
|
|
||||||
* test flow. However, sometimes such an overlay may appear non-deterministically, for example certain cookies consent
|
|
||||||
* dialogs behave this way. In this case,
|
|
||||||
* [page.addLocatorHandler(locator, handler)](https://playwright.dev/docs/api/class-page#page-add-locator-handler)
|
|
||||||
* allows handling an overlay during an action that it would block.
|
|
||||||
*
|
*
|
||||||
* This method registers a handler for an overlay that is executed once the locator is visible on the page. The
|
* When testing a web page, sometimes unexpected overlays like a "Sign up" dialog appear and block actions you want to
|
||||||
* handler should get rid of the overlay so that actions blocked by it can proceed. This is useful for
|
* automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making
|
||||||
* nondeterministic interstitial pages or dialogs, like a cookie consent dialog.
|
* them tricky to handle in automated tests.
|
||||||
*
|
*
|
||||||
* Note that execution time of the handler counts towards the timeout of the action/assertion that executed the
|
* This method lets you set up a special function, called a handler, that activates when it detects that overlay is
|
||||||
* handler.
|
* visible. The handler's job is to remove the overlay, allowing your test to continue as if the overlay wasn't there.
|
||||||
*
|
*
|
||||||
* You can register multiple handlers. However, only a single handler will be running at a time. Any actions inside a
|
* Things to keep in mind:
|
||||||
* handler must not require another handler to run.
|
* - When an overlay is shown predictably, we recommend explicitly waiting for it in your test and dismissing it as
|
||||||
|
* a part of your normal test flow, instead of using
|
||||||
|
* [page.addLocatorHandler(locator, handler)](https://playwright.dev/docs/api/class-page#page-add-locator-handler).
|
||||||
|
* - Playwright checks for the overlay every time before executing or retrying an action that requires an
|
||||||
|
* [actionability check](https://playwright.dev/docs/actionability), or before performing an auto-waiting assertion check. When overlay
|
||||||
|
* is visible, Playwright calls the handler first, and then proceeds with the action/assertion.
|
||||||
|
* - The execution time of the handler counts towards the timeout of the action/assertion that executed the handler.
|
||||||
|
* If your handler takes too long, it might cause timeouts.
|
||||||
|
* - You can register multiple handlers. However, only a single handler will be running at a time. Make sure the
|
||||||
|
* actions within a handler don't depend on another handler.
|
||||||
*
|
*
|
||||||
* **NOTE** Running the interceptor will alter your page state mid-test. For example it will change the currently
|
* **NOTE** Running the handler will alter your page state mid-test. For example it will change the currently focused
|
||||||
* focused element and move the mouse. Make sure that the actions that run after the interceptor are self-contained
|
* element and move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on
|
||||||
* and do not rely on the focus and mouse state. <br /> <br /> For example, consider a test that calls
|
* the focus and mouse state being unchanged. <br /> <br /> For example, consider a test that calls
|
||||||
* [locator.focus([options])](https://playwright.dev/docs/api/class-locator#locator-focus) followed by
|
* [locator.focus([options])](https://playwright.dev/docs/api/class-locator#locator-focus) followed by
|
||||||
* [keyboard.press(key[, options])](https://playwright.dev/docs/api/class-keyboard#keyboard-press). If your handler
|
* [keyboard.press(key[, options])](https://playwright.dev/docs/api/class-keyboard#keyboard-press). If your handler
|
||||||
* clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen
|
* clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen
|
||||||
|
|
@ -1809,17 +1813,18 @@ export interface Page {
|
||||||
* problem. <br /> <br /> Another example is a series of mouse actions, where
|
* problem. <br /> <br /> Another example is a series of mouse actions, where
|
||||||
* [mouse.move(x, y[, options])](https://playwright.dev/docs/api/class-mouse#mouse-move) is followed by
|
* [mouse.move(x, y[, options])](https://playwright.dev/docs/api/class-mouse#mouse-move) is followed by
|
||||||
* [mouse.down([options])](https://playwright.dev/docs/api/class-mouse#mouse-down). Again, when the handler runs
|
* [mouse.down([options])](https://playwright.dev/docs/api/class-mouse#mouse-down). Again, when the handler runs
|
||||||
* between these two actions, the mouse position will be wrong during the mouse down. Prefer methods like
|
* between these two actions, the mouse position will be wrong during the mouse down. Prefer self-contained actions
|
||||||
* [locator.click([options])](https://playwright.dev/docs/api/class-locator#locator-click) that are self-contained.
|
* like [locator.click([options])](https://playwright.dev/docs/api/class-locator#locator-click) that do not rely on
|
||||||
|
* the state being unchanged by a handler.
|
||||||
*
|
*
|
||||||
* **Usage**
|
* **Usage**
|
||||||
*
|
*
|
||||||
* An example that closes a cookie dialog when it appears:
|
* An example that closes a "Sign up to the newsletter" dialog when it appears:
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* // Setup the handler.
|
* // Setup the handler.
|
||||||
* await page.addLocatorHandler(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
|
* await page.addLocatorHandler(page.getByText('Sign up to the newsletter'), async () => {
|
||||||
* await page.getByRole('button', { name: 'Reject all cookies' }).click();
|
* await page.getByRole('button', { name: 'No thanks' }).click();
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* // Write the test as usual.
|
* // Write the test as usual.
|
||||||
|
|
@ -1832,7 +1837,7 @@ export interface Page {
|
||||||
* ```js
|
* ```js
|
||||||
* // Setup the handler.
|
* // Setup the handler.
|
||||||
* await page.addLocatorHandler(page.getByText('Confirm your security details'), async () => {
|
* await page.addLocatorHandler(page.getByText('Confirm your security details'), async () => {
|
||||||
* await page.getByRole('button', 'Remind me later').click();
|
* await page.getByRole('button', { name: 'Remind me later' }).click();
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* // Write the test as usual.
|
* // Write the test as usual.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: "../.eslintrc-with-ts-config.js",
|
extends: "../../.eslintrc-with-ts-config.js",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-core",
|
"name": "@playwright/experimental-ct-core",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright Component Testing Helpers",
|
"description": "Playwright Component Testing Helpers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -26,9 +26,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next",
|
"playwright-core": "1.42.1",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"playwright": "1.42.0-next"
|
"playwright": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export default declare((api: BabelAPI) => {
|
||||||
const ext = path.extname(importNode.source.value);
|
const ext = path.extname(importNode.source.value);
|
||||||
|
|
||||||
// Convert all non-JS imports into refs.
|
// Convert all non-JS imports into refs.
|
||||||
if (!allJsExtensions.has(ext)) {
|
if (artifactExtensions.has(ext)) {
|
||||||
for (const specifier of importNode.specifiers) {
|
for (const specifier of importNode.specifiers) {
|
||||||
if (t.isImportNamespaceSpecifier(specifier))
|
if (t.isImportNamespaceSpecifier(specifier))
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -171,4 +171,29 @@ export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportS
|
||||||
return { localName: specifier.local.name, info: result };
|
return { localName: specifier.local.name, info: result };
|
||||||
}
|
}
|
||||||
|
|
||||||
const allJsExtensions = new Set(['.js', '.jsx', '.cjs', '.mjs', '.ts', '.tsx', '.cts', '.mts', '']);
|
const artifactExtensions = new Set([
|
||||||
|
// Frameworks
|
||||||
|
'.vue',
|
||||||
|
'.svelte',
|
||||||
|
|
||||||
|
// Images
|
||||||
|
'.jpg', '.jpeg',
|
||||||
|
'.png',
|
||||||
|
'.gif',
|
||||||
|
'.svg',
|
||||||
|
'.bmp',
|
||||||
|
'.webp',
|
||||||
|
'.ico',
|
||||||
|
|
||||||
|
// CSS
|
||||||
|
'.css',
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
'.woff', '.woff2',
|
||||||
|
'.ttf',
|
||||||
|
'.otf',
|
||||||
|
'.eot',
|
||||||
|
|
||||||
|
// Other assets
|
||||||
|
'.json',
|
||||||
|
]);
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ImportRegistry } from '../src/injected/importRegistry';
|
|
||||||
|
|
||||||
type JsonPrimitive = string | number | boolean | null;
|
type JsonPrimitive = string | number | boolean | null;
|
||||||
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
||||||
type JsonArray = JsonValue[];
|
type JsonArray = JsonValue[];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-react",
|
"name": "@playwright/experimental-ct-react",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright Component Testing for React",
|
"description": "Playwright Component Testing for React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,11 +29,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-react": "^4.2.1"
|
"@vitejs/plugin-react": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-react": "cli.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
const { _framework } = require('./index');
|
|
||||||
|
|
||||||
initializePlugin(_framework);
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-react17",
|
"name": "@playwright/experimental-ct-react17",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright Component Testing for React",
|
"description": "Playwright Component Testing for React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,11 +29,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-react": "^4.2.1"
|
"@vitejs/plugin-react": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-react17": "cli.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
const { _framework } = require('./index');
|
|
||||||
|
|
||||||
initializePlugin(_framework);
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-solid",
|
"name": "@playwright/experimental-ct-solid",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright Component Testing for Solid",
|
"description": "Playwright Component Testing for Solid",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,14 +29,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"vite-plugin-solid": "^2.7.0"
|
"vite-plugin-solid": "^2.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"solid-js": "^1.7.0"
|
"solid-js": "^1.7.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-solid": "cli.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
const { _framework } = require('./index');
|
|
||||||
|
|
||||||
initializePlugin(_framework);
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-svelte",
|
"name": "@playwright/experimental-ct-svelte",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright Component Testing for Svelte",
|
"description": "Playwright Component Testing for Svelte",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,14 +29,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"svelte": "^4.2.8"
|
"svelte": "^4.2.8"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-svelte": "cli.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,8 @@ function __pwCreateSlots(slots) {
|
||||||
__pwInsert(target, element, anchor);
|
__pwInsert(target, element, anchor);
|
||||||
},
|
},
|
||||||
d: function destroy(detaching) {
|
d: function destroy(detaching) {
|
||||||
if (detaching) __pwDetach(element);
|
if (detaching)
|
||||||
|
__pwDetach(element);
|
||||||
},
|
},
|
||||||
l: __pwNoop,
|
l: __pwNoop,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
const { _framework } = require('./index');
|
|
||||||
|
|
||||||
initializePlugin(_framework);
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-vue",
|
"name": "@playwright/experimental-ct-vue",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright Component Testing for Vue",
|
"description": "Playwright Component Testing for Vue",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,11 +29,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-vue": "^4.2.1"
|
"@vitejs/plugin-vue": "^4.2.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-vue": "cli.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||||
const { _framework } = require('./index');
|
|
||||||
|
|
||||||
initializePlugin(_framework);
|
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/experimental-ct-vue2",
|
"name": "@playwright/experimental-ct-vue2",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "Playwright Component Testing for Vue2",
|
"description": "Playwright Component Testing for Vue2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -29,14 +29,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
"@playwright/experimental-ct-core": "1.42.1",
|
||||||
"@vitejs/plugin-vue2": "^2.2.0"
|
"@vitejs/plugin-vue2": "^2.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vue": "^2.7.14"
|
"vue": "^2.7.14"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js",
|
"playwright": "cli.js"
|
||||||
"pw-vue2": "cli.js"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,5 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('playwright-core/lib/program');
|
const { program } = require('playwright-core/lib/cli/program');
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-firefox",
|
"name": "playwright-firefox",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "A high-level API to automate Firefox",
|
"description": "A high-level API to automate Firefox",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@playwright/test",
|
"name": "@playwright/test",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.42.0-next"
|
"playwright": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,5 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { program } = require('playwright-core/lib/program');
|
const { program } = require('playwright-core/lib/cli/program');
|
||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright-webkit",
|
"name": "playwright-webkit",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "A high-level API to automate WebKit",
|
"description": "A high-level API to automate WebKit",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -30,6 +30,6 @@
|
||||||
"install": "node install.js"
|
"install": "node install.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: '../.eslintrc.js',
|
extends: '../../.eslintrc-with-ts-config.js',
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "playwright",
|
"name": "playwright",
|
||||||
"version": "1.42.0-next",
|
"version": "1.42.1",
|
||||||
"description": "A high-level API to automate web browsers",
|
"description": "A high-level API to automate web browsers",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.42.0-next"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"fsevents": "2.3.2"
|
"fsevents": "2.3.2"
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export function matcherHint(state: ExpectMatcherContext, locator: Locator | unde
|
||||||
if (timeout)
|
if (timeout)
|
||||||
header = colors.red(`Timed out ${timeout}ms waiting for `) + header;
|
header = colors.red(`Timed out ${timeout}ms waiting for `) + header;
|
||||||
if (locator)
|
if (locator)
|
||||||
header += `Locator: ${locator}\n`;
|
header += `Locator: ${String(locator)}\n`;
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export function toBeAttached(
|
||||||
locator: LocatorEx,
|
locator: LocatorEx,
|
||||||
options?: { attached?: boolean, timeout?: number },
|
options?: { attached?: boolean, timeout?: number },
|
||||||
) {
|
) {
|
||||||
const attached = !options || options.attached === undefined || options.attached === true;
|
const attached = !options || options.attached === undefined || options.attached;
|
||||||
const expected = attached ? 'attached' : 'detached';
|
const expected = attached ? 'attached' : 'detached';
|
||||||
const unexpected = attached ? 'detached' : 'attached';
|
const unexpected = attached ? 'detached' : 'attached';
|
||||||
const arg = attached ? '' : '{ attached: false }';
|
const arg = attached ? '' : '{ attached: false }';
|
||||||
|
|
@ -54,7 +54,7 @@ export function toBeChecked(
|
||||||
locator: LocatorEx,
|
locator: LocatorEx,
|
||||||
options?: { checked?: boolean, timeout?: number },
|
options?: { checked?: boolean, timeout?: number },
|
||||||
) {
|
) {
|
||||||
const checked = !options || options.checked === undefined || options.checked === true;
|
const checked = !options || options.checked === undefined || options.checked;
|
||||||
const expected = checked ? 'checked' : 'unchecked';
|
const expected = checked ? 'checked' : 'unchecked';
|
||||||
const unexpected = checked ? 'unchecked' : 'checked';
|
const unexpected = checked ? 'unchecked' : 'checked';
|
||||||
const arg = checked ? '' : '{ checked: false }';
|
const arg = checked ? '' : '{ checked: false }';
|
||||||
|
|
@ -78,7 +78,7 @@ export function toBeEditable(
|
||||||
locator: LocatorEx,
|
locator: LocatorEx,
|
||||||
options?: { editable?: boolean, timeout?: number },
|
options?: { editable?: boolean, timeout?: number },
|
||||||
) {
|
) {
|
||||||
const editable = !options || options.editable === undefined || options.editable === true;
|
const editable = !options || options.editable === undefined || options.editable;
|
||||||
const expected = editable ? 'editable' : 'readOnly';
|
const expected = editable ? 'editable' : 'readOnly';
|
||||||
const unexpected = editable ? 'readOnly' : 'editable';
|
const unexpected = editable ? 'readOnly' : 'editable';
|
||||||
const arg = editable ? '' : '{ editable: false }';
|
const arg = editable ? '' : '{ editable: false }';
|
||||||
|
|
@ -102,7 +102,7 @@ export function toBeEnabled(
|
||||||
locator: LocatorEx,
|
locator: LocatorEx,
|
||||||
options?: { enabled?: boolean, timeout?: number },
|
options?: { enabled?: boolean, timeout?: number },
|
||||||
) {
|
) {
|
||||||
const enabled = !options || options.enabled === undefined || options.enabled === true;
|
const enabled = !options || options.enabled === undefined || options.enabled;
|
||||||
const expected = enabled ? 'enabled' : 'disabled';
|
const expected = enabled ? 'enabled' : 'disabled';
|
||||||
const unexpected = enabled ? 'disabled' : 'enabled';
|
const unexpected = enabled ? 'disabled' : 'enabled';
|
||||||
const arg = enabled ? '' : '{ enabled: false }';
|
const arg = enabled ? '' : '{ enabled: false }';
|
||||||
|
|
@ -136,7 +136,7 @@ export function toBeVisible(
|
||||||
locator: LocatorEx,
|
locator: LocatorEx,
|
||||||
options?: { visible?: boolean, timeout?: number },
|
options?: { visible?: boolean, timeout?: number },
|
||||||
) {
|
) {
|
||||||
const visible = !options || options.visible === undefined || options.visible === true;
|
const visible = !options || options.visible === undefined || options.visible;
|
||||||
const expected = visible ? 'visible' : 'hidden';
|
const expected = visible ? 'visible' : 'hidden';
|
||||||
const unexpected = visible ? 'hidden' : 'visible';
|
const unexpected = visible ? 'hidden' : 'visible';
|
||||||
const arg = visible ? '' : '{ visible: false }';
|
const arg = visible ? '' : '{ visible: false }';
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export async function runTestServer() {
|
||||||
onConnection(request: http.IncomingMessage, url: URL, ws: WebSocket, id: string) {
|
onConnection(request: http.IncomingMessage, url: URL, ws: WebSocket, id: string) {
|
||||||
const dispatcher = new Dispatcher(ws);
|
const dispatcher = new Dispatcher(ws);
|
||||||
ws.on('message', async message => {
|
ws.on('message', async message => {
|
||||||
const { id, method, params } = JSON.parse(message.toString());
|
const { id, method, params } = JSON.parse(String(message));
|
||||||
try {
|
try {
|
||||||
const result = await (dispatcher as any)[method](params);
|
const result = await (dispatcher as any)[method](params);
|
||||||
ws.send(JSON.stringify({ id, result }));
|
ws.send(JSON.stringify({ id, result }));
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,8 @@ function resolveConfigFile(baseConfigFile: string, referencedConfigFile: string)
|
||||||
referencedConfigFile += '.json';
|
referencedConfigFile += '.json';
|
||||||
const currentDir = path.dirname(baseConfigFile);
|
const currentDir = path.dirname(baseConfigFile);
|
||||||
let resolvedConfigFile = path.resolve(currentDir, referencedConfigFile);
|
let resolvedConfigFile = path.resolve(currentDir, referencedConfigFile);
|
||||||
if (referencedConfigFile.indexOf('/') !== -1 && referencedConfigFile.indexOf('.') !== -1 && !fs.existsSync(referencedConfigFile))
|
// TODO: I don't see how this makes sense, delete in the next minor release.
|
||||||
|
if (referencedConfigFile.includes('/') && referencedConfigFile.includes('.') && !fs.existsSync(resolvedConfigFile))
|
||||||
resolvedConfigFile = path.join(currentDir, 'node_modules', referencedConfigFile);
|
resolvedConfigFile = path.join(currentDir, 'node_modules', referencedConfigFile);
|
||||||
return resolvedConfigFile;
|
return resolvedConfigFile;
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +118,7 @@ function loadTsConfig(
|
||||||
let result: LoadedTsConfig = {
|
let result: LoadedTsConfig = {
|
||||||
tsConfigPath: configFilePath,
|
tsConfigPath: configFilePath,
|
||||||
};
|
};
|
||||||
|
// Retain result instance below, so that caching works.
|
||||||
visited.set(configFilePath, result);
|
visited.set(configFilePath, result);
|
||||||
|
|
||||||
if (!fs.existsSync(configFilePath))
|
if (!fs.existsSync(configFilePath))
|
||||||
|
|
@ -137,7 +139,8 @@ function loadTsConfig(
|
||||||
const extendsDir = path.dirname(extendedConfig);
|
const extendsDir = path.dirname(extendedConfig);
|
||||||
base.baseUrl = path.join(extendsDir, base.baseUrl);
|
base.baseUrl = path.join(extendsDir, base.baseUrl);
|
||||||
}
|
}
|
||||||
result = { ...result, ...base, tsConfigPath: configFilePath };
|
// Retain result instance, so that caching works.
|
||||||
|
Object.assign(result, base, { tsConfigPath: configFilePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedConfig = Object.fromEntries(Object.entries({
|
const loadedConfig = Object.fromEntries(Object.entries({
|
||||||
|
|
@ -146,7 +149,8 @@ function loadTsConfig(
|
||||||
allowJs: parsedConfig?.compilerOptions?.allowJs,
|
allowJs: parsedConfig?.compilerOptions?.allowJs,
|
||||||
}).filter(([, value]) => value !== undefined));
|
}).filter(([, value]) => value !== undefined));
|
||||||
|
|
||||||
result = { ...result, ...loadedConfig };
|
// Retain result instance, so that caching works.
|
||||||
|
Object.assign(result, loadedConfig);
|
||||||
|
|
||||||
for (const ref of parsedConfig.references || [])
|
for (const ref of parsedConfig.references || [])
|
||||||
references.push(loadTsConfig(resolveConfigFile(configFilePath, ref.path), references, visited));
|
references.push(loadTsConfig(resolveConfigFile(configFilePath, ref.path), references, visited));
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,8 @@ export function addSuffixToFilePath(filePath: string, suffix: string, customExte
|
||||||
*/
|
*/
|
||||||
export function getContainedPath(parentPath: string, subPath: string = ''): string | null {
|
export function getContainedPath(parentPath: string, subPath: string = ''): string | null {
|
||||||
const resolvedPath = path.resolve(parentPath, subPath);
|
const resolvedPath = path.resolve(parentPath, subPath);
|
||||||
if (resolvedPath === parentPath || resolvedPath.startsWith(parentPath + path.sep)) return resolvedPath;
|
if (resolvedPath === parentPath || resolvedPath.startsWith(parentPath + path.sep))
|
||||||
|
return resolvedPath;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -949,8 +949,8 @@ function filterTree(rootItem: GroupItem, filterText: string, statusFilters: Map<
|
||||||
const filtersStatuses = [...statusFilters.values()].some(Boolean);
|
const filtersStatuses = [...statusFilters.values()].some(Boolean);
|
||||||
|
|
||||||
const filter = (testCase: TestCaseItem) => {
|
const filter = (testCase: TestCaseItem) => {
|
||||||
const title = testCase.tests[0].titlePath().join(' ').toLowerCase();
|
const titleWithTags = [...testCase.tests[0].titlePath(), ...testCase.tests[0].tags].join(' ').toLowerCase();
|
||||||
if (!tokens.every(token => title.includes(token)) && !testCase.tests.some(t => runningTestIds?.has(t.id)))
|
if (!tokens.every(token => titleWithTags.includes(token)) && !testCase.tests.some(t => runningTestIds?.has(t.id)))
|
||||||
return false;
|
return false;
|
||||||
testCase.children = (testCase.children as TestItem[]).filter(test => {
|
testCase.children = (testCase.children as TestItem[]).filter(test => {
|
||||||
return !filtersStatuses || runningTestIds?.has(test.test.id) || statusFilters.get(test.status);
|
return !filtersStatuses || runningTestIds?.has(test.test.id) || statusFilters.get(test.status);
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
|
"@typescript-eslint/no-unnecessary-boolean-literal-compare": 2,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -89,3 +89,9 @@ test('subsequent installs works', async ({ exec }) => {
|
||||||
// of UnhandledPromiseRejection.
|
// of UnhandledPromiseRejection.
|
||||||
await exec('node --unhandled-rejections=strict', path.join('node_modules', '@playwright', 'browser-chromium', 'install.js'));
|
await exec('node --unhandled-rejections=strict', path.join('node_modules', '@playwright', 'browser-chromium', 'install.js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('install playwright-chromium should work', async ({ exec, installedSoftwareOnDisk }) => {
|
||||||
|
await exec('npm i playwright-chromium');
|
||||||
|
await exec('npx playwright install chromium');
|
||||||
|
await exec('node sanity.js playwright-chromium chromium');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,10 @@ it('should work @smoke', async ({ page, browserName }) => {
|
||||||
it('should emit same log twice', async ({ page }) => {
|
it('should emit same log twice', async ({ page }) => {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
page.on('console', m => messages.push(m.text()));
|
page.on('console', m => messages.push(m.text()));
|
||||||
await page.evaluate(() => { for (let i = 0; i < 2; ++i) console.log('hello'); });
|
await page.evaluate(() => {
|
||||||
|
for (let i = 0; i < 2; ++i)
|
||||||
|
console.log('hello');
|
||||||
|
});
|
||||||
expect(messages).toEqual(['hello', 'hello']);
|
expect(messages).toEqual(['hello', 'hello']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -723,7 +723,7 @@ it.describe('page screenshot animations', () => {
|
||||||
el.addEventListener('transitionend', () => {
|
el.addEventListener('transitionend', () => {
|
||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
// Block main thread for 200ms, emulating heavy layout.
|
// Block main thread for 200ms, emulating heavy layout.
|
||||||
while (Date.now() - time < 200) ;
|
while (Date.now() - time < 200) {}
|
||||||
const h1 = document.createElement('h1');
|
const h1 = document.createElement('h1');
|
||||||
h1.textContent = 'woof-woof';
|
h1.textContent = 'woof-woof';
|
||||||
document.body.append(h1);
|
document.body.append(h1);
|
||||||
|
|
|
||||||
|
|
@ -484,3 +484,20 @@ test('should support output accessible name', async ({ page }) => {
|
||||||
await page.setContent(`<label>Output1<output>output</output></label>`);
|
await page.setContent(`<label>Output1<output>output</output></label>`);
|
||||||
await expect(page.getByRole('status', { name: 'Output1' })).toBeVisible();
|
await expect(page.getByRole('status', { name: 'Output1' })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not match scope by default', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<ul>
|
||||||
|
<li aria-label="Parent list">
|
||||||
|
Parent list
|
||||||
|
<ul>
|
||||||
|
<li>child 1</li>
|
||||||
|
<li>child 2</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
`);
|
||||||
|
const children = page.getByRole('listitem', { name: 'Parent list' }).getByRole('listitem');
|
||||||
|
await expect(children).toHaveCount(2);
|
||||||
|
await expect(children).toHaveText(['child 1', 'child 2']);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -511,3 +511,37 @@ test('should allow props children', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should allow import from shared file', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': playwrightCtConfigText,
|
||||||
|
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
|
||||||
|
'playwright/index.ts': ``,
|
||||||
|
'src/component.tsx': `
|
||||||
|
export const Component = (props: { content: string }) => {
|
||||||
|
return <div>{props.content}</div>
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'src/component.shared.tsx': `
|
||||||
|
export const componentMock = { content: 'This is a content.' };
|
||||||
|
`,
|
||||||
|
'src/component.render.tsx': `
|
||||||
|
import {Component} from './component';
|
||||||
|
import {componentMock} from './component.shared';
|
||||||
|
export const ComponentTest = () => {
|
||||||
|
return <Component content={componentMock.content} />;
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'src/component.spec.tsx': `
|
||||||
|
import { expect, test } from '@playwright/experimental-ct-react';
|
||||||
|
import { ComponentTest } from './component.render';
|
||||||
|
import { componentMock } from './component.shared';
|
||||||
|
test('component renders', async ({ mount }) => {
|
||||||
|
const component = await mount(<ComponentTest />);
|
||||||
|
await expect(component).toContainText(componentMock.content)
|
||||||
|
})`
|
||||||
|
}, { workers: 1 });
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const basicTestTree = {
|
||||||
test('passes', () => {});
|
test('passes', () => {});
|
||||||
test('fails', () => { expect(1).toBe(2); });
|
test('fails', () => { expect(1).toBe(2); });
|
||||||
test.describe('suite', () => {
|
test.describe('suite', () => {
|
||||||
test('inner passes', () => {});
|
test('inner passes', { tag: '@smoke' }, () => {});
|
||||||
test('inner fails', () => { expect(1).toBe(2); });
|
test('inner fails', () => { expect(1).toBe(2); });
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
|
|
@ -46,6 +46,16 @@ test('should filter by title', async ({ runUITest }) => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should filter by explicit tags', async ({ runUITest }) => {
|
||||||
|
const { page } = await runUITest(basicTestTree);
|
||||||
|
await page.getByPlaceholder('Filter').fill('@smoke inner');
|
||||||
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
▼ ◯ suite
|
||||||
|
◯ inner passes
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
test('should filter by status', async ({ runUITest }) => {
|
test('should filter by status', async ({ runUITest }) => {
|
||||||
const { page } = await runUITest(basicTestTree);
|
const { page } = await runUITest(basicTestTree);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -438,7 +438,8 @@ test(`should support self signed certificate`, async ({ runInlineTest, httpsServ
|
||||||
test('should send Accept header', async ({ runInlineTest, server }) => {
|
test('should send Accept header', async ({ runInlineTest, server }) => {
|
||||||
let acceptHeader: string | undefined | null = null;
|
let acceptHeader: string | undefined | null = null;
|
||||||
server.setRoute('/hello', (req, res) => {
|
server.setRoute('/hello', (req, res) => {
|
||||||
if (acceptHeader === null) acceptHeader = req.headers.accept;
|
if (acceptHeader === null)
|
||||||
|
acceptHeader = req.headers.accept;
|
||||||
res.end('<html><body>hello</body></html>');
|
res.end('<html><body>hello</body></html>');
|
||||||
});
|
});
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
|
|
@ -661,7 +662,7 @@ test('should check ipv4 and ipv6 with happy eyeballs when URL is passed', async
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
expect(result.output).toContain('Process started');
|
expect(result.output).toContain('Process started');
|
||||||
expect(result.output).toContain(`HTTP HEAD: http://localhost:${port}/`);
|
expect(result.output).toContain(`HTTP GET: http://localhost:${port}/`);
|
||||||
expect(result.output).toContain('WebServer available');
|
expect(result.output).toContain('WebServer available');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -566,7 +566,7 @@ class Type {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedType.args) {
|
if (parsedType.args || parsedType.retType) {
|
||||||
const type = new Type('function');
|
const type = new Type('function');
|
||||||
type.args = [];
|
type.args = [];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -737,7 +737,8 @@ function parseTypeExpression(type) {
|
||||||
if (type[i] === '(') {
|
if (type[i] === '(') {
|
||||||
name = type.substring(0, i);
|
name = type.substring(0, i);
|
||||||
const matching = matchingBracket(type.substring(i), '(', ')');
|
const matching = matchingBracket(type.substring(i), '(', ')');
|
||||||
args = parseTypeExpression(type.substring(i + 1, i + matching - 1));
|
const argsString = type.substring(i + 1, i + matching - 1);
|
||||||
|
args = argsString ? parseTypeExpression(argsString) : null;
|
||||||
i = i + matching;
|
i = i + matching;
|
||||||
if (type[i] === ':') {
|
if (type[i] === ':') {
|
||||||
retType = parseTypeExpression(type.substring(i + 1));
|
retType = parseTypeExpression(type.substring(i + 1));
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,10 @@ class JSLintingService extends LintingService {
|
||||||
* @returns {Promise<LintResult[]>}
|
* @returns {Promise<LintResult[]>}
|
||||||
*/
|
*/
|
||||||
async lint(snippets) {
|
async lint(snippets) {
|
||||||
return Promise.all(snippets.map(async snippet => this._lintSnippet(snippet)));
|
const result = [];
|
||||||
|
for (let i = 0; i < snippets.length; ++i)
|
||||||
|
result.push(await this._lintSnippet(snippets[i]));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue