Compare commits
14 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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: {
|
||||
ecmaVersion: 9,
|
||||
sourceType: "module",
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
extends: [
|
||||
"plugin:react-hooks/recommended"
|
||||
|
|
@ -49,6 +48,7 @@ module.exports = {
|
|||
"arrow-parens": [2, "as-needed"],
|
||||
"prefer-const": 2,
|
||||
"quote-props": [2, "consistent"],
|
||||
"nonblock-statement-body-position": [2, "below"],
|
||||
|
||||
// anti-patterns
|
||||
"no-var": 2,
|
||||
|
|
|
|||
|
|
@ -3146,27 +3146,29 @@ return value resolves to `[]`.
|
|||
## async method: Page.addLocatorHandler
|
||||
* 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.
|
||||
When testing a web page, sometimes unexpected overlays like a coookie consent 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Note that execution time of the handler counts towards the timeout of the action/assertion that executed the handler.
|
||||
|
||||
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
|
||||
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 />
|
||||
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 />
|
||||
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**
|
||||
|
||||
An example that closes a cookie dialog when it appears:
|
||||
An example that closes a cookie consent dialog when it appears:
|
||||
|
||||
```js
|
||||
// Setup the handler.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
```js
|
||||
// Setup the handler.
|
||||
await page.addLocatorHandler(page.getByRole('button', { name: 'Accept all cookies' }), async () => {
|
||||
await page.getByRole('button', { name: 'Reject all cookies' }).click();
|
||||
await page.addLocatorHandler(
|
||||
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.
|
||||
await page.goto('https://example.com');
|
||||
await page.getByRole('button', { name: 'Start here' }).click();
|
||||
await page.goto('https://www.ikea.com/');
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -137,10 +137,12 @@ self.addEventListener('fetch', event => {
|
|||
(async () => {
|
||||
// 1. Try to first serve directly from caches
|
||||
const response = await caches.match(event.request);
|
||||
if (response) return response;
|
||||
if (response)
|
||||
return response;
|
||||
|
||||
// 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
|
||||
if (event.request.url.endsWith('tracker.js')) {
|
||||
|
|
|
|||
86
package-lock.json
generated
86
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "playwright-internal",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "playwright-internal",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
|
@ -8136,10 +8136,10 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright": {
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8153,11 +8153,11 @@
|
|||
},
|
||||
"packages/playwright-browser-chromium": {
|
||||
"name": "@playwright/browser-chromium",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
|
@ -8165,11 +8165,11 @@
|
|||
},
|
||||
"packages/playwright-browser-firefox": {
|
||||
"name": "@playwright/browser-firefox",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
|
@ -8177,22 +8177,22 @@
|
|||
},
|
||||
"packages/playwright-browser-webkit": {
|
||||
"name": "@playwright/browser-webkit",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"packages/playwright-chromium": {
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8202,7 +8202,7 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright-core": {
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
|
|
@ -8213,11 +8213,11 @@
|
|||
},
|
||||
"packages/playwright-ct-core": {
|
||||
"name": "@playwright/experimental-ct-core",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.42.0-next",
|
||||
"playwright-core": "1.42.0-next",
|
||||
"playwright": "1.42.1",
|
||||
"playwright-core": "1.42.1",
|
||||
"vite": "^5.0.12"
|
||||
},
|
||||
"bin": {
|
||||
|
|
@ -8229,15 +8229,14 @@
|
|||
},
|
||||
"packages/playwright-ct-react": {
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-react": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
|
@ -8245,15 +8244,14 @@
|
|||
},
|
||||
"packages/playwright-ct-react17": {
|
||||
"name": "@playwright/experimental-ct-react17",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-react17": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
|
@ -8261,15 +8259,14 @@
|
|||
},
|
||||
"packages/playwright-ct-solid": {
|
||||
"name": "@playwright/experimental-ct-solid",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-solid": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"solid-js": "^1.7.0"
|
||||
|
|
@ -8280,15 +8277,14 @@
|
|||
},
|
||||
"packages/playwright-ct-svelte": {
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-svelte": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^4.2.8"
|
||||
|
|
@ -8299,15 +8295,14 @@
|
|||
},
|
||||
"packages/playwright-ct-vue": {
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-vue": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-vue": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
|
@ -8315,15 +8310,14 @@
|
|||
},
|
||||
"packages/playwright-ct-vue2": {
|
||||
"name": "@playwright/experimental-ct-vue2",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-vue2": "^2.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-vue2": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vue": "^2.7.14"
|
||||
|
|
@ -8368,11 +8362,11 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright-firefox": {
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8383,10 +8377,10 @@
|
|||
},
|
||||
"packages/playwright-test": {
|
||||
"name": "@playwright/test",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.42.0-next"
|
||||
"playwright": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -8396,11 +8390,11 @@
|
|||
}
|
||||
},
|
||||
"packages/playwright-webkit": {
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "playwright-internal",
|
||||
"private": true,
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"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",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright package that automatically installs Chromium",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-firefox",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright package that automatically installs Firefox",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/browser-webkit",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright package that automatically installs WebKit",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -27,6 +27,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program } = require('playwright-core/lib/program');
|
||||
const { program } = require('playwright-core/lib/cli/program');
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-chromium",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "A high-level API to automate Chromium",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
module.exports = {
|
||||
extends: "../.eslintrc-with-ts-config.js",
|
||||
extends: "../../.eslintrc-with-ts-config.js",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-core",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -526,7 +526,7 @@ export async function prepareBrowserContextParams(options: BrowserContextOptions
|
|||
function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
|
||||
if (acceptDownloads === undefined)
|
||||
return undefined;
|
||||
if (acceptDownloads === true)
|
||||
if (acceptDownloads)
|
||||
return 'accept';
|
||||
return 'deny';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -594,7 +594,8 @@ export class CRBrowserContext extends BrowserContext {
|
|||
targetId = (page._delegate as CRPage)._targetId;
|
||||
} else if (page instanceof Frame) {
|
||||
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;
|
||||
} else {
|
||||
throw new Error('page: expected Page or Frame');
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@
|
|||
|
||||
import type { SelectorEngine, SelectorRoot } from './selectorEngine';
|
||||
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 { normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils';
|
||||
import { isInsideScope } from './domUtils';
|
||||
|
||||
type RoleEngineOptions = {
|
||||
role: string;
|
||||
|
|
@ -126,27 +125,26 @@ function validateAttributes(attrs: AttributeSelectorPart[], role: string): RoleE
|
|||
}
|
||||
|
||||
function queryRole(scope: SelectorRoot, options: RoleEngineOptions, internal: boolean): Element[] {
|
||||
const doc = scope.nodeType === 9 /* Node.DOCUMENT_NODE */ ? scope as Document : scope.ownerDocument;
|
||||
const elements = doc ? getElementsByRole(doc, options.role) : [];
|
||||
return elements.filter(element => {
|
||||
if (!isInsideScope(scope, element))
|
||||
return false;
|
||||
const result: Element[] = [];
|
||||
const match = (element: Element) => {
|
||||
if (getAriaRole(element) !== options.role)
|
||||
return;
|
||||
if (options.selected !== undefined && getAriaSelected(element) !== options.selected)
|
||||
return false;
|
||||
return;
|
||||
if (options.checked !== undefined && getAriaChecked(element) !== options.checked)
|
||||
return false;
|
||||
return;
|
||||
if (options.pressed !== undefined && getAriaPressed(element) !== options.pressed)
|
||||
return false;
|
||||
return;
|
||||
if (options.expanded !== undefined && getAriaExpanded(element) !== options.expanded)
|
||||
return false;
|
||||
return;
|
||||
if (options.level !== undefined && getAriaLevel(element) !== options.level)
|
||||
return false;
|
||||
return;
|
||||
if (options.disabled !== undefined && getAriaDisabled(element) !== options.disabled)
|
||||
return false;
|
||||
return;
|
||||
if (!options.includeHidden) {
|
||||
const isHidden = isElementHiddenForAria(element);
|
||||
if (isHidden)
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
if (options.name !== undefined) {
|
||||
// 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 === '=')
|
||||
options.nameOp = '*=';
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -845,51 +845,11 @@ function getAccessibleNameFromAssociatedLabels(labels: Iterable<HTMLLabelElement
|
|||
})).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 cacheAccessibleNameHidden: Map<Element, string> | undefined;
|
||||
let cacheIsHidden: Map<Element, boolean> | undefined;
|
||||
let cachePseudoContentBefore: Map<Element, string> | undefined;
|
||||
let cachePseudoContentAfter: Map<Element, string> | undefined;
|
||||
let cacheElementsByRole: Map<string, Element[]> | undefined;
|
||||
let cacheElementsByRoleDocument: Document | undefined;
|
||||
let cachesCounter = 0;
|
||||
|
||||
export function beginAriaCaches() {
|
||||
|
|
@ -908,7 +868,5 @@ export function endAriaCaches() {
|
|||
cacheIsHidden = undefined;
|
||||
cachePseudoContentBefore = 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
|
||||
function getInstanceName(instance: VueVNode): string {
|
||||
const name = getComponentTypeName(instance.type || {});
|
||||
if (name) return name;
|
||||
if (instance.root === instance) return 'Root';
|
||||
for (const key in instance.parent?.type?.components)
|
||||
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);
|
||||
if (name)
|
||||
return name;
|
||||
if (instance.root === instance)
|
||||
return 'Root';
|
||||
for (const key in instance.parent?.type?.components) {
|
||||
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';
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
function getFragmentRootElements(vnode: any): Element[] {
|
||||
if (!vnode.children) return [];
|
||||
if (!vnode.children)
|
||||
return [];
|
||||
|
||||
const list = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ function preprocess(str: string): number[] {
|
|||
if (code === 0xd && str.charCodeAt(i + 1) === 0xa) {
|
||||
code = 0xa; i++;
|
||||
}
|
||||
if (code === 0xd || code === 0xc) code = 0xa;
|
||||
if (code === 0x0) code = 0xfffd;
|
||||
if (code === 0xd || code === 0xc)
|
||||
code = 0xa;
|
||||
if (code === 0x0)
|
||||
code = 0xfffd;
|
||||
if (between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i + 1), 0xdc00, 0xdfff)) {
|
||||
// Decode a surrogate pair into an astral codepoint.
|
||||
const lead = code - 0xd800;
|
||||
|
|
@ -63,7 +65,8 @@ function preprocess(str: string): 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.
|
||||
code -= Math.pow(2, 16);
|
||||
const lead = Math.floor(code / Math.pow(2, 10)) + 0xd800;
|
||||
|
|
@ -107,8 +110,10 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
num = 1;
|
||||
i += num;
|
||||
code = codepoint(i);
|
||||
if (newline(code)) incrLineno();
|
||||
else column += num;
|
||||
if (newline(code))
|
||||
incrLineno();
|
||||
else
|
||||
column += num;
|
||||
// console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
|
||||
return true;
|
||||
};
|
||||
|
|
@ -125,7 +130,8 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
return true;
|
||||
};
|
||||
const eof = function(codepoint?: number): boolean {
|
||||
if (codepoint === undefined) codepoint = code;
|
||||
if (codepoint === undefined)
|
||||
codepoint = code;
|
||||
return codepoint === -1;
|
||||
};
|
||||
const donothing = function() { };
|
||||
|
|
@ -138,12 +144,14 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
consumeComments();
|
||||
consume();
|
||||
if (whitespace(code)) {
|
||||
while (whitespace(next())) consume();
|
||||
while (whitespace(next()))
|
||||
consume();
|
||||
return new WhitespaceToken();
|
||||
} else if (code === 0x22) {return consumeAStringToken();} else if (code === 0x23) {
|
||||
if (namechar(next()) || areAValidEscape(next(1), next(2))) {
|
||||
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();
|
||||
return token;
|
||||
} else {
|
||||
|
|
@ -288,7 +296,8 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
const str = consumeAName();
|
||||
if (str.toLowerCase() === 'url' && next() === 0x28) {
|
||||
consume();
|
||||
while (whitespace(next(1)) && whitespace(next(2))) consume();
|
||||
while (whitespace(next(1)) && whitespace(next(2)))
|
||||
consume();
|
||||
if (next() === 0x22 || next() === 0x27)
|
||||
return new FunctionToken(str);
|
||||
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 {
|
||||
if (endingCodePoint === undefined) endingCodePoint = code;
|
||||
if (endingCodePoint === undefined)
|
||||
endingCodePoint = code;
|
||||
let string = '';
|
||||
while (consume()) {
|
||||
if (code === endingCodePoint || eof()) {
|
||||
|
|
@ -331,13 +341,16 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
|
||||
const consumeAURLToken = function(): CSSTokenInterface {
|
||||
const token = new URLToken('');
|
||||
while (whitespace(next())) consume();
|
||||
if (eof(next())) return token;
|
||||
while (whitespace(next()))
|
||||
consume();
|
||||
if (eof(next()))
|
||||
return token;
|
||||
while (consume()) {
|
||||
if (code === 0x29 || eof()) {
|
||||
return token;
|
||||
} else if (whitespace(code)) {
|
||||
while (whitespace(next())) consume();
|
||||
while (whitespace(next()))
|
||||
consume();
|
||||
if (next() === 0x29 || eof(next())) {
|
||||
consume();
|
||||
return token;
|
||||
|
|
@ -379,9 +392,11 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (whitespace(next())) consume();
|
||||
if (whitespace(next()))
|
||||
consume();
|
||||
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;
|
||||
} else if (eof()) {
|
||||
return 0xfffd;
|
||||
|
|
@ -391,8 +406,10 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
};
|
||||
|
||||
const areAValidEscape = function(c1: number, c2: number) {
|
||||
if (c1 !== 0x5c) return false;
|
||||
if (newline(c2)) return false;
|
||||
if (c1 !== 0x5c)
|
||||
return false;
|
||||
if (newline(c2))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
const startsWithAValidEscape = function() {
|
||||
|
|
@ -416,11 +433,14 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
|
||||
const wouldStartANumber = function(c1: number, c2: number, c3: number) {
|
||||
if (c1 === 0x2b || c1 === 0x2d) {
|
||||
if (digit(c2)) return true;
|
||||
if (c2 === 0x2e && digit(c3)) return true;
|
||||
if (digit(c2))
|
||||
return true;
|
||||
if (c2 === 0x2e && digit(c3))
|
||||
return true;
|
||||
return false;
|
||||
} else if (c1 === 0x2e) {
|
||||
if (digit(c2)) return true;
|
||||
if (digit(c2))
|
||||
return true;
|
||||
return false;
|
||||
} else if (digit(c1)) {
|
||||
return true;
|
||||
|
|
@ -519,7 +539,8 @@ export function tokenize(str1: string): CSSTokenInterface[] {
|
|||
while (!eof(next())) {
|
||||
tokens.push(consumeAToken());
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
return new Promise(resolve => {
|
||||
onLog?.(`HTTP HEAD: ${url}`);
|
||||
onLog?.(`HTTP GET: ${url}`);
|
||||
httpRequest({
|
||||
method: 'HEAD',
|
||||
url: url.toString(),
|
||||
headers: { Accept: '*/*' },
|
||||
rejectUnauthorized: !ignoreHTTPSErrors
|
||||
|
|
|
|||
43
packages/playwright-core/types/types.d.ts
vendored
43
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -1781,26 +1781,28 @@ export interface Page {
|
|||
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
|
||||
* 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.
|
||||
* When testing a web page, sometimes unexpected overlays like a coookie consent 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.
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* Note that execution time of the handler counts towards the timeout of the action/assertion that executed the
|
||||
* handler.
|
||||
* 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
|
||||
* [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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* **NOTE** 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. <br /> <br /> For example, consider a test that calls
|
||||
* **NOTE** 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 /> For example, consider a test that calls
|
||||
* [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
|
||||
* clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen
|
||||
|
|
@ -1809,12 +1811,13 @@ export interface Page {
|
|||
* 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.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
|
||||
* [locator.click([options])](https://playwright.dev/docs/api/class-locator#locator-click) that are self-contained.
|
||||
* between these two actions, the mouse position will be wrong during the mouse down. Prefer self-contained actions
|
||||
* 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**
|
||||
*
|
||||
* An example that closes a cookie dialog when it appears:
|
||||
* An example that closes a cookie consent dialog when it appears:
|
||||
*
|
||||
* ```js
|
||||
* // Setup the handler.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
module.exports = {
|
||||
extends: "../.eslintrc-with-ts-config.js",
|
||||
extends: "../../.eslintrc-with-ts-config.js",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-core",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright Component Testing Helpers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -26,9 +26,9 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next",
|
||||
"playwright-core": "1.42.1",
|
||||
"vite": "^5.0.12",
|
||||
"playwright": "1.42.0-next"
|
||||
"playwright": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default declare((api: BabelAPI) => {
|
|||
const ext = path.extname(importNode.source.value);
|
||||
|
||||
// Convert all non-JS imports into refs.
|
||||
if (!allJsExtensions.has(ext)) {
|
||||
if (artifactExtensions.has(ext)) {
|
||||
for (const specifier of importNode.specifiers) {
|
||||
if (t.isImportNamespaceSpecifier(specifier))
|
||||
continue;
|
||||
|
|
@ -171,4 +171,29 @@ export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportS
|
|||
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.
|
||||
*/
|
||||
|
||||
import type { ImportRegistry } from '../src/injected/importRegistry';
|
||||
|
||||
type JsonPrimitive = string | number | boolean | null;
|
||||
type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
||||
type JsonArray = JsonValue[];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -29,11 +29,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-react": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||
const { _framework } = require('./index');
|
||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||
|
||||
initializePlugin(_framework);
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-react17",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright Component Testing for React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -29,11 +29,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-react": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-react17": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||
const { _framework } = require('./index');
|
||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||
|
||||
initializePlugin(_framework);
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-solid",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright Component Testing for Solid",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -29,14 +29,13 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"solid-js": "^1.7.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-solid": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||
const { _framework } = require('./index');
|
||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||
|
||||
initializePlugin(_framework);
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright Component Testing for Svelte",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -29,14 +29,13 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^4.2.8"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-svelte": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ function __pwCreateSlots(slots) {
|
|||
__pwInsert(target, element, anchor);
|
||||
},
|
||||
d: function destroy(detaching) {
|
||||
if (detaching) __pwDetach(element);
|
||||
if (detaching)
|
||||
__pwDetach(element);
|
||||
},
|
||||
l: __pwNoop,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||
const { _framework } = require('./index');
|
||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||
|
||||
initializePlugin(_framework);
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright Component Testing for Vue",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -29,11 +29,10 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-vue": "^4.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-vue": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program, initializePlugin } = require('@playwright/experimental-ct-core/lib/program');
|
||||
const { _framework } = require('./index');
|
||||
const { program } = require('@playwright/experimental-ct-core/lib/program');
|
||||
|
||||
initializePlugin(_framework);
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/experimental-ct-vue2",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "Playwright Component Testing for Vue2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -29,14 +29,13 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/experimental-ct-core": "1.42.0-next",
|
||||
"@playwright/experimental-ct-core": "1.42.1",
|
||||
"@vitejs/plugin-vue2": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vue": "^2.7.14"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js",
|
||||
"pw-vue2": "cli.js"
|
||||
"playwright": "cli.js"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program } = require('playwright-core/lib/program');
|
||||
const { program } = require('playwright-core/lib/cli/program');
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-firefox",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "A high-level API to automate Firefox",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@playwright/test",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
},
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"playwright": "1.42.0-next"
|
||||
"playwright": "1.42.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program } = require('playwright-core/lib/program');
|
||||
const { program } = require('playwright-core/lib/cli/program');
|
||||
program.parse(process.argv);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright-webkit",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "A high-level API to automate WebKit",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -30,6 +30,6 @@
|
|||
"install": "node install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
module.exports = {
|
||||
extends: '../.eslintrc.js',
|
||||
extends: '../../.eslintrc-with-ts-config.js',
|
||||
rules: {
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "playwright",
|
||||
"version": "1.42.0-next",
|
||||
"version": "1.42.1",
|
||||
"description": "A high-level API to automate web browsers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
},
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.42.0-next"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export function matcherHint(state: ExpectMatcherContext, locator: Locator | unde
|
|||
if (timeout)
|
||||
header = colors.red(`Timed out ${timeout}ms waiting for `) + header;
|
||||
if (locator)
|
||||
header += `Locator: ${locator}\n`;
|
||||
header += `Locator: ${String(locator)}\n`;
|
||||
return header;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function toBeAttached(
|
|||
locator: LocatorEx,
|
||||
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 unexpected = attached ? 'detached' : 'attached';
|
||||
const arg = attached ? '' : '{ attached: false }';
|
||||
|
|
@ -54,7 +54,7 @@ export function toBeChecked(
|
|||
locator: LocatorEx,
|
||||
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 unexpected = checked ? 'unchecked' : 'checked';
|
||||
const arg = checked ? '' : '{ checked: false }';
|
||||
|
|
@ -78,7 +78,7 @@ export function toBeEditable(
|
|||
locator: LocatorEx,
|
||||
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 unexpected = editable ? 'readOnly' : 'editable';
|
||||
const arg = editable ? '' : '{ editable: false }';
|
||||
|
|
@ -102,7 +102,7 @@ export function toBeEnabled(
|
|||
locator: LocatorEx,
|
||||
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 unexpected = enabled ? 'disabled' : 'enabled';
|
||||
const arg = enabled ? '' : '{ enabled: false }';
|
||||
|
|
@ -136,7 +136,7 @@ export function toBeVisible(
|
|||
locator: LocatorEx,
|
||||
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 unexpected = visible ? 'hidden' : 'visible';
|
||||
const arg = visible ? '' : '{ visible: false }';
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export async function runTestServer() {
|
|||
onConnection(request: http.IncomingMessage, url: URL, ws: WebSocket, id: string) {
|
||||
const dispatcher = new Dispatcher(ws);
|
||||
ws.on('message', async message => {
|
||||
const { id, method, params } = JSON.parse(message.toString());
|
||||
const { id, method, params } = JSON.parse(String(message));
|
||||
try {
|
||||
const result = await (dispatcher as any)[method](params);
|
||||
ws.send(JSON.stringify({ id, result }));
|
||||
|
|
|
|||
|
|
@ -101,7 +101,8 @@ function resolveConfigFile(baseConfigFile: string, referencedConfigFile: string)
|
|||
referencedConfigFile += '.json';
|
||||
const currentDir = path.dirname(baseConfigFile);
|
||||
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);
|
||||
return resolvedConfigFile;
|
||||
}
|
||||
|
|
@ -117,6 +118,7 @@ function loadTsConfig(
|
|||
let result: LoadedTsConfig = {
|
||||
tsConfigPath: configFilePath,
|
||||
};
|
||||
// Retain result instance below, so that caching works.
|
||||
visited.set(configFilePath, result);
|
||||
|
||||
if (!fs.existsSync(configFilePath))
|
||||
|
|
@ -137,7 +139,8 @@ function loadTsConfig(
|
|||
const extendsDir = path.dirname(extendedConfig);
|
||||
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({
|
||||
|
|
@ -146,7 +149,8 @@ function loadTsConfig(
|
|||
allowJs: parsedConfig?.compilerOptions?.allowJs,
|
||||
}).filter(([, value]) => value !== undefined));
|
||||
|
||||
result = { ...result, ...loadedConfig };
|
||||
// Retain result instance, so that caching works.
|
||||
Object.assign(result, loadedConfig);
|
||||
|
||||
for (const ref of parsedConfig.references || [])
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ module.exports = {
|
|||
},
|
||||
rules: {
|
||||
'@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.
|
||||
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 }) => {
|
||||
const messages = [];
|
||||
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']);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -723,7 +723,7 @@ it.describe('page screenshot animations', () => {
|
|||
el.addEventListener('transitionend', () => {
|
||||
const time = Date.now();
|
||||
// Block main thread for 200ms, emulating heavy layout.
|
||||
while (Date.now() - time < 200) ;
|
||||
while (Date.now() - time < 200) {}
|
||||
const h1 = document.createElement('h1');
|
||||
h1.textContent = 'woof-woof';
|
||||
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 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.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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -438,7 +438,8 @@ test(`should support self signed certificate`, async ({ runInlineTest, httpsServ
|
|||
test('should send Accept header', async ({ runInlineTest, server }) => {
|
||||
let acceptHeader: string | undefined | null = null;
|
||||
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>');
|
||||
});
|
||||
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.passed).toBe(1);
|
||||
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');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@ class Type {
|
|||
return type;
|
||||
}
|
||||
|
||||
if (parsedType.args) {
|
||||
if (parsedType.args || parsedType.retType) {
|
||||
const type = new Type('function');
|
||||
type.args = [];
|
||||
// @ts-ignore
|
||||
|
|
@ -737,7 +737,8 @@ function parseTypeExpression(type) {
|
|||
if (type[i] === '(') {
|
||||
name = type.substring(0, 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;
|
||||
if (type[i] === ':') {
|
||||
retType = parseTypeExpression(type.substring(i + 1));
|
||||
|
|
|
|||
|
|
@ -180,7 +180,10 @@ class JSLintingService extends LintingService {
|
|||
* @returns {Promise<LintResult[]>}
|
||||
*/
|
||||
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