mirror of
https://github.com/microsoft/playwright-java.git
synced 2025-09-08 21:01:00 +00:00
feat: implement page.tap() (#106)
This commit is contained in:
parent
b895f90fd6
commit
4a18f5b1b7
@ -457,7 +457,16 @@ public class FrameImpl extends ChannelOwner implements Frame {
|
||||
|
||||
@Override
|
||||
public void tap(String selector, TapOptions options) {
|
||||
|
||||
if (options == null) {
|
||||
options = new TapOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
params.remove("modifiers");
|
||||
if (options.modifiers != null) {
|
||||
params.add("modifiers", Serialization.toProtocol(options.modifiers));
|
||||
}
|
||||
params.addProperty("selector", selector);
|
||||
sendMessage("tap", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -39,6 +39,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
private final KeyboardImpl keyboard;
|
||||
private final MouseImpl mouse;
|
||||
private final AccessibilityImpl accessibility;
|
||||
private final TouchscreenImpl touchscreen;
|
||||
private Viewport viewport;
|
||||
private final Router routes = new Router();
|
||||
private final Set<FrameImpl> frames = new LinkedHashSet<>();
|
||||
@ -56,6 +57,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
mainFrame.page = this;
|
||||
keyboard = new KeyboardImpl(this);
|
||||
mouse = new MouseImpl(this);
|
||||
touchscreen = new TouchscreenImpl(this);
|
||||
accessibility = new AccessibilityImpl(this);
|
||||
frames.add(mainFrame);
|
||||
timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings);
|
||||
@ -653,7 +655,7 @@ public class PageImpl extends ChannelOwner implements Page {
|
||||
|
||||
@Override
|
||||
public Touchscreen touchscreen() {
|
||||
return null;
|
||||
return touchscreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Touchscreen;
|
||||
|
||||
class TouchscreenImpl implements Touchscreen {
|
||||
private final PageImpl page;
|
||||
|
||||
TouchscreenImpl(PageImpl page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tap(int x, int y) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("x", x);
|
||||
params.addProperty("y", y);
|
||||
page.sendMessage("touchscreenTap", params);
|
||||
}
|
||||
}
|
@ -111,11 +111,15 @@ public class TestBase {
|
||||
playwright = null;
|
||||
}
|
||||
|
||||
BrowserContext createContext() {
|
||||
return browser.newContext();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void createContextAndPage() {
|
||||
server.reset();
|
||||
httpsServer.reset();
|
||||
context = browser.newContext();
|
||||
context = createContext();
|
||||
page = context.newPage();
|
||||
}
|
||||
|
||||
|
197
playwright/src/test/java/com/microsoft/playwright/TestTap.java
Normal file
197
playwright/src/test/java/com/microsoft/playwright/TestTap.java
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.microsoft.playwright.Utils.mapOf;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestTap extends TestBase {
|
||||
|
||||
@Override
|
||||
BrowserContext createContext() {
|
||||
return browser.newContext(new Browser.NewContextOptions().withHasTouch(true));
|
||||
}
|
||||
|
||||
private JSHandle trackEvents(ElementHandle target) {
|
||||
return target.evaluateHandle("target => {\n" +
|
||||
" const events = [];\n" +
|
||||
" for (const event of [\n" +
|
||||
" 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'click',\n" +
|
||||
" 'pointercancel', 'pointerdown', 'pointerenter', 'pointerleave', 'pointermove', 'pointerout', 'pointerover', 'pointerup',\n" +
|
||||
" 'touchstart', 'touchend', 'touchmove', 'touchcancel'])\n" +
|
||||
" target.addEventListener(event, () => events.push(event), false);\n" +
|
||||
" return events;\n" +
|
||||
"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendAllOfTheCorrectEvents() {
|
||||
page.setContent("<div id='a' style='background: lightblue; width: 50px; height: 50px'>a</div>\n" +
|
||||
"<div id='b' style='background: pink; width: 50px; height: 50px'>b</div>");
|
||||
page.tap("#a");
|
||||
JSHandle eventsHandle = trackEvents(page.querySelector("#b"));
|
||||
page.tap("#b");
|
||||
// webkit doesnt send pointerenter or pointerleave or mouseout
|
||||
assertEquals(asList("pointerover", "pointerenter",
|
||||
"pointerdown", "touchstart",
|
||||
"pointerup", "pointerout",
|
||||
"pointerleave", "touchend",
|
||||
"mouseover", "mouseenter",
|
||||
"mousemove", "mousedown",
|
||||
"mouseup", "click"),
|
||||
eventsHandle.jsonValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSendMouseEventsTouchstartIsCanceled() {
|
||||
page.setContent("<div style='width: 50px; height: 50px; background: red'>");
|
||||
page.evaluate("() => {\n" +
|
||||
" // touchstart is not cancelable unless passive is false\n" +
|
||||
" document.addEventListener('touchstart', t => t.preventDefault(), {passive: false});\n" +
|
||||
" }");
|
||||
JSHandle eventsHandle = trackEvents(page.querySelector("div"));
|
||||
page.tap("div");
|
||||
assertEquals(asList("pointerover", "pointerenter",
|
||||
"pointerdown", "touchstart",
|
||||
"pointerup", "pointerout",
|
||||
"pointerleave", "touchend"),
|
||||
eventsHandle.jsonValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSendMouseEventsWhenTouchendIsCanceled() {
|
||||
page.setContent("<div style='width: 50px; height: 50px; background: red'>");
|
||||
page.evaluate("() => document.addEventListener('touchend', t => t.preventDefault())");
|
||||
JSHandle eventsHandle = trackEvents(page.querySelector("div"));
|
||||
page.tap("div");
|
||||
assertEquals(asList("pointerover", "pointerenter",
|
||||
"pointerdown", "touchstart",
|
||||
"pointerup", "pointerout",
|
||||
"pointerleave", "touchend"),
|
||||
eventsHandle.jsonValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWaitForANavigationCausedByATap() throws InterruptedException {
|
||||
page.navigate(server.EMPTY_PAGE);
|
||||
page.setContent("<a href='/intercept-this.html'>link</a>;");
|
||||
Semaphore responseWritten = new Semaphore(0);
|
||||
List<String> events = Collections.synchronizedList(new ArrayList<>());
|
||||
server.setRoute("/intercept-this.html", exchange -> {
|
||||
// make sure the tap doesnt resolve too early
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
events.add("interrupted");
|
||||
}
|
||||
exchange.getResponseHeaders().add("Content-Type", "application/octet-stream");
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
|
||||
writer.write("foo");
|
||||
}
|
||||
events.add("sent response");
|
||||
responseWritten.release();
|
||||
});
|
||||
page.tap("a");
|
||||
events.add("tap finished");
|
||||
responseWritten.acquire();
|
||||
assertEquals(asList("sent response", "tap finished"), events);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithModifiers() {
|
||||
page.setContent("hello world");
|
||||
page.evaluate("() => {\n" +
|
||||
" window.touchPromise = new Promise(resolve => {\n" +
|
||||
" document.addEventListener('touchstart', event => {\n" +
|
||||
" resolve(event.altKey);\n" +
|
||||
" }, {passive: false});\n" +
|
||||
" });\n" +
|
||||
"}");
|
||||
page.tap("body", new Page.TapOptions().withModifiers(Keyboard.Modifier.ALT));
|
||||
Object altKey = page.evaluate("() => window.touchPromise");
|
||||
assertEquals(true, altKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendWellFormedTouchPoints() {
|
||||
page.evaluate("() => {\n" +
|
||||
" window.touchStartPromise = new Promise(resolve => {\n" +
|
||||
" document.addEventListener('touchstart', event => {\n" +
|
||||
" resolve([...event.touches].map(t => ({\n" +
|
||||
" identifier: t.identifier,\n" +
|
||||
" clientX: t.clientX,\n" +
|
||||
" clientY: t.clientY,\n" +
|
||||
" pageX: t.pageX,\n" +
|
||||
" pageY: t.pageY,\n" +
|
||||
" radiusX: 'radiusX' in t ? t.radiusX : t['webkitRadiusX'],\n" +
|
||||
" radiusY: 'radiusY' in t ? t.radiusY : t['webkitRadiusY'],\n" +
|
||||
" rotationAngle: 'rotationAngle' in t ? t.rotationAngle : t['webkitRotationAngle'],\n" +
|
||||
" force: 'force' in t ? t.force : t['webkitForce'],\n" +
|
||||
" })));\n" +
|
||||
" }, false);\n" +
|
||||
" })\n" +
|
||||
" }");
|
||||
page.evaluate("() => {\n" +
|
||||
" window.touchEndPromise = new Promise(resolve => {\n" +
|
||||
" document.addEventListener('touchend', event => {\n" +
|
||||
" resolve([...event.touches].map(t => ({\n" +
|
||||
" identifier: t.identifier,\n" +
|
||||
" clientX: t.clientX,\n" +
|
||||
" clientY: t.clientY,\n" +
|
||||
" pageX: t.pageX,\n" +
|
||||
" pageY: t.pageY,\n" +
|
||||
" radiusX: 'radiusX' in t ? t.radiusX : t['webkitRadiusX'],\n" +
|
||||
" radiusY: 'radiusY' in t ? t.radiusY : t['webkitRadiusY'],\n" +
|
||||
" rotationAngle: 'rotationAngle' in t ? t.rotationAngle : t['webkitRotationAngle'],\n" +
|
||||
" force: 'force' in t ? t.force : t['webkitForce'],\n" +
|
||||
" })));\n" +
|
||||
" }, false);\n" +
|
||||
" })\n" +
|
||||
" }");
|
||||
|
||||
page.touchscreen().tap(40, 60);
|
||||
Object touchStart = page.evaluate("() => window.touchStartPromise");
|
||||
assertEquals(asList(mapOf(
|
||||
"clientX", 40,
|
||||
"clientY", 60,
|
||||
"force", 1,
|
||||
"identifier", 0,
|
||||
"pageX", 40,
|
||||
"pageY", 60,
|
||||
"radiusX", 1,
|
||||
"radiusY", 1,
|
||||
"rotationAngle", 0
|
||||
)), touchStart);
|
||||
Object touchEnd = page.evaluate("() => window.touchEndPromise");
|
||||
assertEquals(Collections.emptyList(), touchEnd);
|
||||
}
|
||||
|
||||
void shouldWaitUntilAnElementIsVisibleToTapIt() {
|
||||
// Ignored in sync api.
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user