1
0
mirror of synced 2026-08-05 23:16:54 +00:00

feat: roll 1.42.0-alpha driver, implement page.addLocatorHandler (#1494)

This commit is contained in:
Yury Semikhatsky
2024-02-15 08:49:45 -08:00
committed by GitHub
parent 99ab89917f
commit 7f2a5fe9af
18 changed files with 439 additions and 29 deletions
+2 -2
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->121.0.6167.57<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->122.0.6261.29<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->121.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->122.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details.
@@ -60,7 +60,7 @@ public interface BrowserContext extends AutoCloseable {
/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
* console.dir}.
*
* <p> The arguments passed into {@code console.log} and the page are available on the {@code ConsoleMessage} event handler
* argument.
@@ -1779,8 +1779,8 @@ public interface ElementHandle extends JSHandle {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
@@ -1809,8 +1809,8 @@ public interface ElementHandle extends JSHandle {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
@@ -3991,8 +3991,8 @@ public interface Frame {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
@@ -4020,8 +4020,8 @@ public interface Frame {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
@@ -152,8 +152,8 @@ public interface Keyboard {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -197,8 +197,8 @@ public interface Keyboard {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -4084,8 +4084,8 @@ public interface Locator {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
@@ -4123,8 +4123,8 @@ public interface Locator {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
@@ -80,7 +80,7 @@ public interface Page extends AutoCloseable {
/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
* console.dir}.
*
* <p> The arguments passed into {@code console.log} are available on the {@code ConsoleMessage} event handler argument.
*
@@ -5804,8 +5804,8 @@ public interface Page extends AutoCloseable {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -5847,8 +5847,8 @@ public interface Page extends AutoCloseable {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -5895,6 +5895,77 @@ public interface Page extends AutoCloseable {
* @since v1.9
*/
List<ElementHandle> querySelectorAll(String selector);
/**
* 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, {@link Page#addLocatorHandler Page.addLocatorHandler()} allows handling an overlay during an
* action that it would block.
*
* <p> 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.
*
* <p> Note that execution time of the handler counts towards the timeout of the action/assertion that executed the handler.
*
* <p> 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.
*
* <p> <strong>NOTE:</strong> 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 {@link Locator#focus Locator.focus()}
* followed by {@link Keyboard#press 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 {@link Locator#press
* Locator.press()} instead to avoid this problem. <br /> <br /> Another example is a series of mouse actions, where {@link
* Mouse#move Mouse.move()} is followed by {@link Mouse#down Mouse.down()}. Again, when the handler runs between these two
* actions, the mouse position will be wrong during the mouse down. Prefer methods like {@link Locator#click
* Locator.click()} that are self-contained.
*
* <p> **Usage**
*
* <p> An example that closes a cookie dialog when it appears:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all cookies")), () => {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Reject all cookies")).click();
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* <p> An example that skips the "Confirm your security details" page when it is shown:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.getByText("Confirm your security details")), () => {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* <p> An example with a custom callback on every actionability check. It uses a {@code <body>} locator that is always visible,
* so the handler is called before every actionability check:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.locator("body")), () => {
* page.evaluate("window.removeObstructionsForTestIfNeeded()");
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* @param locator Locator that triggers the handler.
* @param handler Function that should be run once {@code locator} appears. This function should get rid of the element that blocks
* actions like click.
* @since v1.42
*/
void addLocatorHandler(Locator locator, Runnable handler);
/**
* This method reloads the current page, in the same way as if the user had triggered a browser refresh. Returns the main
* resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
@@ -186,7 +186,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
throw new PlaywrightException("Failed to read from file", e);
}
String content = new String(encoded, StandardCharsets.UTF_8);
content += "//# sourceURL=" + options.path.toString().replace("\n", "");
content = addSourceUrlToScript(content, options.path);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addScriptTag", jsonOptions);
@@ -73,6 +73,14 @@ public class HARRouter {
if ("fulfill".equals(action)) {
int status = response.get("status").getAsInt();
// If the response status is -1, the request was canceled or stalled, so we just stall it here.
// See https://github.com/microsoft/playwright/issues/29311.
// TODO: it'd be better to abort such requests, but then we likely need to respect the timing,
// because the request might have been stalled for a long time until the very end of the
// test when HAR was recorded but we'd abort it immediately.
if (status == -1) {
return;
}
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
route.fulfill(new Route.FulfillOptions()
@@ -49,6 +49,8 @@ public class PageImpl extends ChannelOwner implements Page {
private ViewportSize viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final Map<Integer, Runnable> locatorHandlers = new HashMap<>();
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
@@ -170,6 +172,9 @@ public class PageImpl extends ChannelOwner implements Page {
frame.parentFrame.childFrames.remove(frame);
}
listeners.notify(EventType.FRAMEDETACHED, frame);
} else if ("locatorHandlerTriggered".equals(event)) {
int uid = params.get("uid").getAsInt();
onLocatorHandlerTriggered(uid);
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = browserContext;
@@ -523,6 +528,34 @@ public class PageImpl extends ChannelOwner implements Page {
return withLogging("Page.querySelectorAll", () -> mainFrame.querySelectorAllImpl(selector));
}
@Override
public void addLocatorHandler(Locator locator, Runnable handler) {
LocatorImpl locatorImpl = (LocatorImpl) locator;
if (locatorImpl.frame != mainFrame) {
throw new PlaywrightException("Locator must belong to the main frame of this page");
}
withLogging("Page.addLocatorHandler", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params);
int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, handler);
});
}
private void onLocatorHandlerTriggered(int uid) {
try {
Runnable handler = locatorHandlers.get(uid);
if (handler != null) {
handler.run();
}
} finally {
JsonObject params = new JsonObject();
params.addProperty("uid", uid);
sendMessageAsync("resolveLocatorHandlerNoReply", params);
}
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
@@ -544,7 +577,8 @@ public class PageImpl extends ChannelOwner implements Page {
withLogging("Page.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8));
String script = addSourceUrlToScript(new String(bytes, UTF_8), path);
addInitScriptImpl(script);
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
@@ -341,4 +341,8 @@ public class Utils {
}
return flags;
}
static String addSourceUrlToScript(String source, Path path) {
return source + "\n//# sourceURL=" + path.toString().replace("\n", "");
}
}
@@ -33,6 +33,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Utils.copy;
@@ -469,4 +470,37 @@ public class TestBrowserContextHar extends TestBase {
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
@Test
void shouldIgnoreAbortedRequests(@TempDir Path tmpDir) {
Path path = tmpDir.resolve("test.har");
try (BrowserContext context1 = browser.newContext()) {
server.setRoute("/x", exchange -> exchange.close());
context1.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUpdate(true));
Page page1 = context1.newPage();
page1.navigate(server.EMPTY_PAGE);
Future<Server.Request> reqPromise = server.futureRequest("/x");
Object req = page1.evaluate("url => fetch(url).catch(e => 'cancelled')", server.PREFIX + "/x");
assertEquals("cancelled", req);
}
server.reset();
try (BrowserContext context2 = browser.newContext()) {
server.setRoute("/x", exchange -> {
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 4);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("test");
}
});
context2.routeFromHAR(path);
Page page2 = context2.newPage();
page2.navigate(server.EMPTY_PAGE);
page2.evaluate("url => {\n" +
" fetch(url).catch(e => 'cancelled').then(r => { window.result = r; })\n" +
"}", server.PREFIX + "/x");
page2.waitForTimeout(1000);
assertNull(page.evaluate("window.result"));
}
}
}
@@ -151,7 +151,7 @@ public class TestBrowserContextStorageState extends TestBase {
" origin: 'http://localhost:" + server.PORT + "',\n" +
" localStorage: [{\n" +
" name: 'foo',\n" +
" value: '" + (char)55934 + "'\n" +
" value: '" + (char)65533 + "'\n" +
" }]\n" +
"}]}", new Gson().fromJson(storageState, JsonObject.class));
}
@@ -66,7 +66,7 @@ public class TestBrowserTypeConnect extends TestBase {
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path dir = driver.driverPath().getParent();
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
String cliJs = dir.resolve("package/cli.js").toString();
// We launch node process directly instead of using playwright.sh script as killing the script
// process will leave node process running and killing it would be more hassle.
ProcessBuilder pb = new ProcessBuilder(node, cliJs, "launch-server", "--browser", browserType.name());
@@ -0,0 +1,156 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestPageAddLocatorHandler extends TestBase {
@Test
void shouldWork() {
page.navigate(server.PREFIX + "/input/handle-locator.html");
int[] beforeCount = {0};
int[] afterCount = {0};
page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
++beforeCount[0];
page.locator("#close").click();
++afterCount[0];
});
String[][] argsList = {
{"mouseover", "1"},
{"mouseover", "1", "capture"},
{"mouseover", "2"},
{"mouseover", "2", "capture"},
{"pointerover", "1"},
{"pointerover", "1", "capture"},
{"none", "1"},
{"remove", "1"},
{"hide", "1"},
};
for (String[] args : argsList) {
page.locator("#aside").hover();
beforeCount[0] = 0;
afterCount[0] = 0;
page.evaluate("(args) => {\n" +
" window.clicked = 0;\n" +
" window.setupAnnoyingInterstitial(...args);\n" +
"}", args);
assertEquals(0, beforeCount[0]);
assertEquals(0, afterCount[0]);
page.locator("#target").click();
assertEquals(Integer.parseInt(args[1]), beforeCount[0]);
assertEquals(Integer.parseInt(args[1]), afterCount[0]);
assertEquals(1, page.evaluate("window.clicked"));
assertThat(page.locator("#interstitial")).not().isVisible();
}
}
@Test
void shouldWorkWithCustomCheck() {
page.navigate(server.PREFIX + "/input/handle-locator.html");
page.addLocatorHandler(page.locator("body"), () -> {
if (page.getByText("This interstitial covers the button").isVisible())
page.locator("#close").click();
});
String[][] argsList = {
{"mouseover", "2"},
{"none", "1"},
{"remove", "1"},
{"hide", "1"},
};
for (String[] args : argsList) {
page.hover("#aside");
page.evaluate("(args) => {\n" +
" window.clicked = 0;\n" +
" window.setupAnnoyingInterstitial(...args);\n" +
"}", args);
page.locator("#target").click();
assertEquals(1, page.evaluate("window.clicked"));
assertThat(page.locator("#interstitial")).not().isVisible();
}
}
@Test
void shouldWorkWithLocatorHover() {
page.navigate(server.PREFIX + "/input/handle-locator.html");
page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
page.locator("#close").click();
});
page.locator("#aside").hover();
page.evaluate("() => {\n" +
" window.setupAnnoyingInterstitial('pointerover', 1, 'capture');\n" +
" }");
page.locator("#target").hover();
assertThat(page.locator("#interstitial")).not().isVisible();
assertEquals("rgb(255, 255, 0)", page.evalOnSelector("#target", "element => getComputedStyle(element).backgroundColor"));
}
@Test
void shouldNotWorkWithForceTrue() {
page.navigate(server.PREFIX + "/input/handle-locator.html");
page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
page.locator("#close").click();
});
page.locator("#aside").hover();
page.evaluate("() => {\n" +
" window.setupAnnoyingInterstitial('none', 1);\n" +
" }");
page.locator("#target").click(new Locator.ClickOptions().setForce(true).setTimeout(2000));
assertTrue(page.locator("#interstitial").isVisible());
assertNull(page.evaluate("window.clicked"));
}
@Test
void shouldThrowWhenPageCloses() {
page.navigate(server.PREFIX + "/input/handle-locator.html");
page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
page.close();
});
page.locator("#aside").hover();
page.evaluate("() => {\n" +
" window.clicked = 0;\n" +
" window.setupAnnoyingInterstitial('mouseover', 1);\n" +
" }");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.locator("#target").click());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
void shouldWorkWithToBeVisible() {
page.navigate(server.PREFIX + "/input/handle-locator.html");
int[] called = {0};
page.addLocatorHandler(page.getByText("This interstitial covers the button"), () -> {
++called[0];
page.locator("#close").click();
});
page.evaluate("() => {\n" +
" window.clicked = 0;\n" +
" window.setupAnnoyingInterstitial('remove', 1);\n" +
"}");
assertThat(page.locator("#target")).isVisible();
assertThat(page.locator("#interstitial")).not().isVisible();
assertEquals(1, called[0]);
}
}
@@ -0,0 +1,21 @@
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestPageAddScriptTag extends TestBase {
@Test
@DisabledIf(value="com.microsoft.playwright.TestBase#isWebKit", disabledReason="Upstream behavior")
void shouldIncludeSourceURLWhenPathIsProvided() {
page.navigate(server.EMPTY_PAGE);
Path path = Paths.get("src/test/resources/injectedfile.js");
page.addScriptTag(new Page.AddScriptTagOptions().setPath(path));
String result = (String) page.evaluate("() => window['__injectedError'].stack");
assertTrue(result.contains("resources/injectedfile.js"));
}
}
@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html>
<head>
<title>Interstitial test</title>
</head>
<body>
<style>
body {
position: relative;
}
#target.removed {
display: none;
}
#target.hidden {
visibility: hidden;
}
#target:hover {
background: yellow;
}
#interstitial {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 300px;
border: 1px solid black;
background: rgba(255, 180, 180);
display: none;
}
#interstitial.visible {
display: block;
}
#close {
margin: 50px;
}
</style>
<div><button id="target">Click me</button></div>
<div id="aside">A place on the side to hover</div>
<div id="interstitial">
<div>This interstitial covers the button</div>
<button id="close">Close the interstitial</button>
</div>
<script>
const target = document.querySelector('#target');
const interstitial = document.querySelector('#interstitial');
const close = document.querySelector('#close');
target.addEventListener('click', () => {
window.clicked = (window.clicked ?? 0) + 1;
}, false);
close.addEventListener('click', () => {
interstitial.classList.remove('visible');
target.classList.remove('hidden');
target.classList.remove('removed');
});
let timesToShow = 0;
function setupAnnoyingInterstitial(event, times, capture) {
timesToShow = times;
const listener = () => {
timesToShow--;
interstitial.classList.add('visible');
interstitial.getBoundingClientRect();
if (!timesToShow && event !== 'none')
target.removeEventListener(event, listener, capture === 'capture');
};
if (event === 'hide') {
target.classList.add('hidden');
listener();
} else if (event === 'remove') {
target.classList.add('removed');
listener();
} else if (event === 'none') {
listener();
} else {
target.addEventListener(event, listener, capture === 'capture');
}
}
</script>
</body>
</html>
+1 -1
View File
@@ -1 +1 @@
1.41.0
1.42.0-alpha-1707967288000