Skip to content

Commit a7d7cc8

Browse files
authored
test: start generating element handle tests (microsoft#16)
1 parent de6b486 commit a7d7cc8

27 files changed

+545
-50
lines changed

playwright/browser_context.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -130,18 +130,19 @@ async def exposeFunction(self, name: str, binding: Callable[..., Any]) -> None:
130130
await self.exposeBinding(name, lambda source, *args: binding(*args))
131131

132132
async def route(self, match: URLMatch, handler: RouteHandler) -> None:
133-
self._routes.append(dict(matcher=URLMatcher(match), handler=handler))
133+
self._routes.append(RouteHandlerEntry(URLMatcher(match), handler))
134134
if len(self._routes) == 1:
135135
await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=True))
136136

137137
async def unroute(self, match: URLMatch, handler: Optional[RouteHandler]) -> None:
138-
self._routes = filter(lambda r: r['matcher'].match != match or (handler and r['handler'] != handler), self._routes)
138+
self._routes = filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes)
139139
if len(self._routes) == 0:
140140
await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=False))
141141

142142
async def waitForEvent(self, event: str) -> None:
143143
# TODO: implement timeout race
144144
future = self._scope._loop.create_future()
145+
self.once(event, lambda e: future.set_result(e))
145146
pending_event = PendingWaitEvent(event, future)
146147
self._pending_wait_for_events.append(pending_event)
147148
result = await future

playwright/helper.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,6 @@ class FilePayload(TypedDict):
4141
class FrameMatch(TypedDict):
4242
url: URLMatch
4343
name: str
44-
class PendingWaitEvent(TypedDict):
45-
event: str
46-
future: asyncio.Future
47-
48-
class RouteHandlerEntry(TypedDict):
49-
matcher: "URLMatcher"
50-
handler: RouteHandler
5144
class SelectOption(TypedDict):
5245
value: Optional[str]
5346
label: Optional[str]
@@ -108,3 +101,13 @@ def locals_to_params(args: Dict) -> Dict:
108101
if args[key] != None:
109102
copy[key] = args[key]
110103
return copy
104+
105+
class PendingWaitEvent:
106+
def __init__(self, event: str, future: asyncio.Future):
107+
self.event = event
108+
self.future = future
109+
110+
class RouteHandlerEntry:
111+
def __init__(self, matcher: URLMatcher, handler: RouteHandler):
112+
self.matcher = matcher
113+
self.handler = handler

playwright/page.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ def _on_frame_navigated(self, frame: Frame, url: str, name: str) -> None:
124124

125125
def _on_route(self, route: Route, request: Request) -> None:
126126
for handler_entry in self._routes:
127-
if handler_entry['matcher'].matches(request.url):
128-
handler_entry['handler'](route, request)
127+
if handler_entry.matcher.matches(request.url):
128+
handler_entry.handler(route, request)
129129
return
130130
self._browser_context._on_route(route, request)
131131

@@ -310,6 +310,7 @@ def predicate(request: Request):
310310
async def waitForEvent(self, event: str) -> Any:
311311
# TODO: support timeout
312312
future = self._scope._loop.create_future()
313+
self.once(event, lambda e: future.set_result(e))
313314
pending_event = PendingWaitEvent(event, future)
314315
self._pending_wait_for_events.append(pending_event)
315316
result = await future
@@ -350,12 +351,12 @@ async def addInitScript(self, source: str = None, path: str = None) -> None:
350351
await self._channel.send('addInitScript', dict(source=source))
351352

352353
async def route(self, match: URLMatch, handler: RouteHandler) -> None:
353-
self._routes.append(dict(matcher=URLMatcher(match), handler=handler))
354+
self._routes.append(RouteHandlerEntry(URLMatcher(match), handler))
354355
if len(self._routes) == 1:
355356
await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=True))
356357

357358
async def unroute(self, match: URLMatch, handler: Optional[RouteHandler]) -> None:
358-
self._routes = filter(lambda r: r['matcher'].match != match or (handler and r['handler'] != handler), self._routes)
359+
self._routes = filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes)
359360
if len(self._routes) == 0:
360361
await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=False))
361362

playwright/transport.py

-4
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ async def _run(self) -> None:
4747

4848
if 'DEBUGP' in os.environ:
4949
print('\x1b[33mRECV>\x1b[0m', json.dumps(obj, indent=2))
50-
if 'DEBUG' in os.environ:
51-
print('\x1b[33mRECV>\x1b[0m', obj.get('method'))
5250
self.on_message(obj)
5351
except asyncio.IncompleteReadError:
5452
break
@@ -58,8 +56,6 @@ def send(self, message: Dict) -> None:
5856
msg = json.dumps(message)
5957
if 'DEBUGP' in os.environ:
6058
print('\x1b[32mSEND>\x1b[0m', json.dumps(message, indent=2))
61-
if 'DEBUG' in os.environ:
62-
print('\x1b[32mSEND>\x1b[0m', message.get('method'))
6359
data = bytes(msg, 'utf-8')
6460
self._output.write(len(data).to_bytes(4, byteorder='little', signed=False))
6561
self._output.write(data)
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<iframe src='./redirect-my-parent.html'></iframe>

tests/assets/frames/frame.html

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<link rel='stylesheet' href='./style.css'>
2+
<script src='./script.js' type='text/javascript'></script>
3+
<style>
4+
body {
5+
height: 100px;
6+
margin: 8px;
7+
border: 0;
8+
background-color: #555;
9+
10+
}
11+
div {
12+
line-height: 18px;
13+
}
14+
</style>
15+
<div>Hi, I'm frame</div>

tests/assets/frames/frameset.html

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<frameset>
2+
<frameset>
3+
<frame src='./frame.html'></frame>
4+
<frame src='about:blank'></frame>
5+
</frameset>
6+
<frame src='/empty.html'></frame>
7+
<frame></frame>
8+
</frameset>
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<style>
2+
body {
3+
display: flex;
4+
height: 500px;
5+
margin: 8px;
6+
}
7+
8+
body iframe {
9+
flex-grow: 1;
10+
flex-shrink: 1;
11+
border: 0;
12+
background-color: green;
13+
}
14+
15+
::-webkit-scrollbar{
16+
display: none;
17+
}
18+
</style>
19+
<script>
20+
async function attachFrame(frameId, url) {
21+
var frame = document.createElement('iframe');
22+
frame.src = url;
23+
frame.id = frameId;
24+
document.body.appendChild(frame);
25+
await new Promise(x => frame.onload = x);
26+
return 'kazakh';
27+
}
28+
</script>
29+
<iframe src='./two-frames.html' name='2frames'></iframe>
30+
<iframe src='./frame.html' name='aframe'></iframe>

tests/assets/frames/one-frame.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<iframe src='./frame.html'></iframe>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script>
2+
window.parent.location = './one-frame.html';
3+
</script>

tests/assets/frames/script.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('Cheers!');

tests/assets/frames/style.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
div {
2+
color: blue;
3+
}

tests/assets/frames/two-frames.html

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<style>
2+
body {
3+
display: flex;
4+
flex-direction: column;
5+
height: 400px;
6+
margin: 8px;
7+
}
8+
9+
body iframe {
10+
flex-grow: 1;
11+
flex-shrink: 1;
12+
border: 0;
13+
}
14+
</style>
15+
<iframe src='./frame.html' name='uno'></iframe>
16+
<iframe src='./frame.html' name='dos'></iframe>

tests/assets/grid.html

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<script>
2+
document.addEventListener('DOMContentLoaded', function() {
3+
function generatePalette(amount) {
4+
var result = [];
5+
var hueStep = 360 / amount;
6+
for (var i = 0; i < amount; ++i)
7+
result.push('hsl(' + (hueStep * i) + ', 100%, 90%)');
8+
return result;
9+
}
10+
11+
var palette = generatePalette(100);
12+
for (var i = 0; i < 200; ++i) {
13+
var box = document.createElement('div');
14+
box.classList.add('box');
15+
box.style.setProperty('background-color', palette[i % palette.length]);
16+
var x = i;
17+
do {
18+
var digit = x % 10;
19+
x = (x / 10)|0;
20+
var img = document.createElement('img');
21+
img.src = `./digits/${digit}.png`;
22+
box.insertBefore(img, box.firstChild);
23+
} while (x);
24+
document.body.appendChild(box);
25+
}
26+
});
27+
</script>
28+
29+
<style>
30+
31+
body {
32+
margin: 0;
33+
padding: 0;
34+
}
35+
36+
.box {
37+
font-family: arial;
38+
display: inline-flex;
39+
align-items: center;
40+
justify-content: center;
41+
margin: 0;
42+
padding: 0;
43+
width: 50px;
44+
height: 50px;
45+
box-sizing: border-box;
46+
border: 1px solid darkgray;
47+
}
48+
49+
::-webkit-scrollbar {
50+
display: none;
51+
}
52+
</style>
File renamed without changes.

tests/assets/input/scrollable.html

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Scrollable test</title>
5+
</head>
6+
<body>
7+
<script src='mouse-helper.js'></script>
8+
<script>
9+
for (let i = 0; i < 100; i++) {
10+
let button = document.createElement('button');
11+
button.textContent = i + ': not clicked';
12+
button.id = 'button-' + i;
13+
button.onclick = () => button.textContent = 'clicked';
14+
button.oncontextmenu = event => {
15+
event.preventDefault();
16+
button.textContent = 'context menu';
17+
}
18+
document.body.appendChild(button);
19+
document.body.appendChild(document.createElement('br'));
20+
}
21+
</script>
22+
</body>
23+
</html>
File renamed without changes.

tests/assets/shadow.html

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script>
2+
3+
let h1 = null;
4+
let button = null;
5+
let clicked = false;
6+
7+
window.addEventListener('DOMContentLoaded', () => {
8+
const shadowRoot = document.body.attachShadow({mode: 'open'});
9+
h1 = document.createElement('h1');
10+
h1.textContent = 'Hellow Shadow DOM v1';
11+
button = document.createElement('button');
12+
button.textContent = 'Click';
13+
button.addEventListener('click', () => clicked = true);
14+
shadowRoot.appendChild(h1);
15+
shadowRoot.appendChild(button);
16+
});
17+
</script>

tests/conftest.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717

1818
import pytest
1919
import playwright
20-
from .server import PORT, HTTPRequestHandler
20+
from .server import server as server_object, HTTPRequestHandler
21+
from .utils import utils as utils_object
2122

2223
# Will mark all the tests as async
2324
def pytest_collection_modifyitems(items):
@@ -50,9 +51,17 @@ async def page(context):
5051
yield page
5152
await page.close()
5253

54+
@pytest.fixture
55+
def server():
56+
yield server_object
57+
58+
@pytest.fixture
59+
def utils():
60+
yield utils_object
61+
5362
@pytest.fixture(autouse=True, scope='session')
5463
async def start_http_server():
55-
httpd = http.server.HTTPServer(('', PORT), HTTPRequestHandler)
64+
httpd = http.server.HTTPServer(('', server_object.PORT), HTTPRequestHandler)
5665
threading.Thread(target=httpd.serve_forever).start()
5766
yield
5867
httpd.shutdown()

tests/server.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@
1515
import http.server
1616
import os
1717

18-
PORT = 8907
19-
EMPTY_PAGE = f'http://localhost:{PORT}/empty.html'
20-
PREFIX = f'http://localhost:{PORT}'
18+
class Server:
19+
def __init__(self):
20+
self.PORT = 8907
21+
self.EMPTY_PAGE = f'http://localhost:{self.PORT}/empty.html'
22+
self.PREFIX = f'http://localhost:{self.PORT}'
23+
self.CROSS_PROCESS_PREFIX = f'http://127.0.0.1:{self.PORT}'
24+
25+
server = Server()
2126

2227
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
2328
def __init__(self, *args, **kwargs):

tests/test_click.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@
1313
# limitations under the License.
1414

1515
from playwright.helper import Error
16-
from .server import PREFIX
1716

1817

19-
async def test_click_the_button(page):
20-
await page.goto(f'{PREFIX}/button.html')
18+
async def test_click_the_button(page, server):
19+
await page.goto(f'{server.PREFIX}/input/button.html')
2120
await page.click('button')
2221
assert await page.evaluate('result') == 'Clicked'
2322

24-
async def test_select_the_text_by_triple_clicking(page):
25-
await page.goto(f'{PREFIX}/textarea.html')
23+
async def test_select_the_text_by_triple_clicking(page, server):
24+
await page.goto(f'{server.PREFIX}/input/textarea.html')
2625
text = 'This is the text that we are going to try to select. Let\'s see how it goes.'
2726
await page.fill('textarea', text)
2827
await page.click('textarea', clickCount=3)
@@ -31,8 +30,8 @@ async def test_select_the_text_by_triple_clicking(page):
3130
return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
3231
}''') == text
3332

34-
async def test_not_wait_with_force(page):
35-
await page.goto(f'{PREFIX}/button.html')
33+
async def test_not_wait_with_force(page, server):
34+
await page.goto(f'{server.PREFIX}/input/button.html')
3635
await page.evalOnSelector('button', 'b => b.style.display = "none"')
3736
error = None
3837
try:
@@ -42,8 +41,8 @@ async def test_not_wait_with_force(page):
4241
assert 'Element is not visible' in error.message
4342
assert await page.evaluate('result') == 'Was not clicked'
4443

45-
async def test_click_the_button_with_px_border_with_offset(page):
46-
await page.goto(f'{PREFIX}/button.html')
44+
async def test_click_the_button_with_px_border_with_offset(page, server):
45+
await page.goto(f'{server.PREFIX}/input/button.html')
4746
await page.evalOnSelector('button', 'button => button.style.borderWidth = "8px"')
4847
await page.click('button', position=dict(x=20, y=10))
4948
assert await page.evaluate('result') == 'Clicked'

0 commit comments

Comments
 (0)