From a3c0e8211b39dad029b4f04e8306dad19355eb92 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 00:20:35 +0200 Subject: [PATCH 1/9] tests: added network tests and refactored test server --- .github/workflows/ci.yml | 2 +- playwright/serializers.py | 7 +- tests/assets/one-style.css | 3 + tests/assets/one-style.html | 2 + tests/assets/pptr.png | Bin 0 -> 6138 bytes tests/assets/serviceworkers/empty/sw.html | 3 + tests/assets/serviceworkers/empty/sw.js | 0 tests/assets/serviceworkers/fetch/style.css | 3 + tests/assets/serviceworkers/fetch/sw.html | 5 + tests/assets/serviceworkers/fetch/sw.js | 7 + .../assets/serviceworkers/fetchdummy/sw.html | 12 + tests/assets/serviceworkers/fetchdummy/sw.js | 15 + tests/assets/simple.json | 1 + tests/conftest.py | 19 +- tests/server.py | 94 ++-- tests/test_add_init_script.py | 11 +- tests/test_download.py | 23 +- tests/test_network.py | 450 ++++++++++++++++++ 18 files changed, 605 insertions(+), 52 deletions(-) create mode 100644 tests/assets/one-style.css create mode 100644 tests/assets/one-style.html create mode 100644 tests/assets/pptr.png create mode 100644 tests/assets/serviceworkers/empty/sw.html create mode 100644 tests/assets/serviceworkers/empty/sw.js create mode 100644 tests/assets/serviceworkers/fetch/style.css create mode 100644 tests/assets/serviceworkers/fetch/sw.html create mode 100644 tests/assets/serviceworkers/fetch/sw.js create mode 100644 tests/assets/serviceworkers/fetchdummy/sw.html create mode 100644 tests/assets/serviceworkers/fetchdummy/sw.js create mode 100644 tests/assets/simple.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4897e580..dc471d4e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,4 +51,4 @@ jobs: - name: Build package run: python build_package.py - name: Test - run: pytest --browser=${{ matrix.browser }} + run: pytest --browser=${{ matrix.browser }} -n auto diff --git a/playwright/serializers.py b/playwright/serializers.py index e01539212..fa7e0ca1e 100644 --- a/playwright/serializers.py +++ b/playwright/serializers.py @@ -1,9 +1,10 @@ from typing import List, Union -from playwright.helper import FilePayload -from os import path +import os import mimetypes import base64 +from playwright.helper import FilePayload + def normalize_file_payloads( files: Union[str, FilePayload, List[Union[str, FilePayload]]] @@ -18,7 +19,7 @@ def normalize_file_payloads( if isinstance(item, str): with open(item, mode="rb") as fd: file: FilePayload = { - "name": path.basename(item), + "name": os.path.basename(item), "mimeType": mimetypes.guess_type(item)[0] or "application/octet-stream", "buffer": base64.b64encode(fd.read()).decode(), diff --git a/tests/assets/one-style.css b/tests/assets/one-style.css new file mode 100644 index 000000000..7b26410d8 --- /dev/null +++ b/tests/assets/one-style.css @@ -0,0 +1,3 @@ +body { + background-color: pink; +} diff --git a/tests/assets/one-style.html b/tests/assets/one-style.html new file mode 100644 index 000000000..4760f2b9f --- /dev/null +++ b/tests/assets/one-style.html @@ -0,0 +1,2 @@ + +
hello, world!
diff --git a/tests/assets/pptr.png b/tests/assets/pptr.png new file mode 100644 index 0000000000000000000000000000000000000000..65d87c68e65902c058af18d2a595fc89f423f4ff GIT binary patch literal 6138 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 W{|p>bpZvrb0Q9tsH0#uzQ2zrM7gd%3 literal 0 HcmV?d00001 diff --git a/tests/assets/serviceworkers/empty/sw.html b/tests/assets/serviceworkers/empty/sw.html new file mode 100644 index 000000000..bef85d985 --- /dev/null +++ b/tests/assets/serviceworkers/empty/sw.html @@ -0,0 +1,3 @@ + diff --git a/tests/assets/serviceworkers/empty/sw.js b/tests/assets/serviceworkers/empty/sw.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/assets/serviceworkers/fetch/style.css b/tests/assets/serviceworkers/fetch/style.css new file mode 100644 index 000000000..7b26410d8 --- /dev/null +++ b/tests/assets/serviceworkers/fetch/style.css @@ -0,0 +1,3 @@ +body { + background-color: pink; +} diff --git a/tests/assets/serviceworkers/fetch/sw.html b/tests/assets/serviceworkers/fetch/sw.html new file mode 100644 index 000000000..a9d28acb0 --- /dev/null +++ b/tests/assets/serviceworkers/fetch/sw.html @@ -0,0 +1,5 @@ + + diff --git a/tests/assets/serviceworkers/fetch/sw.js b/tests/assets/serviceworkers/fetch/sw.js new file mode 100644 index 000000000..d44c7eab9 --- /dev/null +++ b/tests/assets/serviceworkers/fetch/sw.js @@ -0,0 +1,7 @@ +self.addEventListener('fetch', event => { + event.respondWith(fetch(event.request)); +}); + +self.addEventListener('activate', event => { + event.waitUntil(clients.claim()); +}); diff --git a/tests/assets/serviceworkers/fetchdummy/sw.html b/tests/assets/serviceworkers/fetchdummy/sw.html new file mode 100644 index 000000000..7042b2f9e --- /dev/null +++ b/tests/assets/serviceworkers/fetchdummy/sw.html @@ -0,0 +1,12 @@ + diff --git a/tests/assets/serviceworkers/fetchdummy/sw.js b/tests/assets/serviceworkers/fetchdummy/sw.js new file mode 100644 index 000000000..ad4a3bea1 --- /dev/null +++ b/tests/assets/serviceworkers/fetchdummy/sw.js @@ -0,0 +1,15 @@ +self.addEventListener('fetch', event => { + if (event.request.url.endsWith('.html') || event.request.url.includes('passthrough')) { + event.respondWith(fetch(event.request)); + return; + } + const slash = event.request.url.lastIndexOf('/'); + const name = event.request.url.substring(slash + 1); + const blob = new Blob(["responseFromServiceWorker:" + name], {type : 'text/css'}); + const response = new Response(blob, { "status" : 200 , "statusText" : "OK" }); + event.respondWith(response); +}); + +self.addEventListener('activate', event => { + event.waitUntil(clients.claim()); +}); diff --git a/tests/assets/simple.json b/tests/assets/simple.json new file mode 100644 index 000000000..6d9590305 --- /dev/null +++ b/tests/assets/simple.json @@ -0,0 +1 @@ +{"foo": "bar"} diff --git a/tests/conftest.py b/tests/conftest.py index 753975a6c..d1b610f49 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,8 +14,9 @@ import asyncio import pytest -import playwright +import sys +import playwright from .server import server as server_object from .utils import utils as utils_object @@ -60,6 +61,7 @@ async def context(browser): context = await browser.newContext() yield context await context.close() + assert len(browser.contexts) == 0 @pytest.fixture @@ -112,6 +114,21 @@ def is_chromium(browser_name): return browser_name == "chromium" +@pytest.fixture(scope="session") +def is_win(browser_name): + return sys.platform == "win32" + + +@pytest.fixture(scope="session") +def is_linux(browser_name): + return sys.platform == "linux" + + +@pytest.fixture(scope="session") +def is_mac(browser_name): + return sys.platform == "darwin" + + @pytest.fixture(autouse=True) def skip_by_browser(request, browser_name): skip_browsers_names = [] diff --git a/tests/server.py b/tests/server.py index 913ecf580..eb03a3110 100644 --- a/tests/server.py +++ b/tests/server.py @@ -14,16 +14,15 @@ import asyncio from contextlib import closing - +from http import HTTPStatus import os import socket import threading -import binascii +import gzip +import mimetypes from twisted.internet import reactor -from twisted.web.static import File -from twisted.web import server as web_server, resource -from twisted.web._responses import UNAUTHORIZED +from twisted.web import http def find_free_port(): @@ -33,19 +32,6 @@ def find_free_port(): return s.getsockname()[1] -class NoResponseResource: - @staticmethod - def render(request): - return b"" - - -class UnauthorizedResource(resource.ErrorPage): - def __init__(self, message="Sorry, resource is unauthorized."): - resource.ErrorPage.__init__( - self, UNAUTHORIZED, "Unauthorized Resource", message - ) - - class Server: def __init__(self): self.PORT = find_free_port() @@ -53,16 +39,23 @@ def __init__(self): self.PREFIX = f"http://localhost:{self.PORT}" self.CROSS_PROCESS_PREFIX = f"http://127.0.0.1:{self.PORT}" + def __repr__(self) -> str: + return self.PREFIX + def start(self): request_subscribers = {} auth = {} routes = {} + gzip_routes = set() self.request_subscribers = request_subscribers self.auth = auth self.routes = routes + self.gzip_routes = gzip_routes + static_path = os.path.join(os.path.dirname(__file__), "assets") - class CustomFileServer(File): - def getChild(self, path, request): + class TestServerHTTPHandler(http.Request): + def process(self): + request = self uri_path = request.uri.decode() if request_subscribers.get(uri_path): request_subscribers[uri_path].set_result(request) @@ -74,25 +67,44 @@ def getChild(self, path, request): ) creds_correct = False if authorization_header: - creds = binascii.a2b_base64( - authorization_header[0].split(" ")[1].encode() + b"===" - ).decode() - creds_correct = ":".join(auth.get(uri_path)) == creds + creds_correct = auth.get(uri_path) == ( + request.getUser(), + request.getPassword(), + ) if not (authorization_header or creds_correct): - request.responseHeaders.addRawHeader( - b"www-authenticate", b'Basic realm="Secure Area"' + request.setHeader( + b"www-authenticate", 'Basic realm="Secure Area"' ) - return UnauthorizedResource() + request.setResponseCode(HTTPStatus.UNAUTHORIZED) + request.finish() + return if routes.get(uri_path): routes[uri_path](request) - return NoResponseResource() - - return super().getChild(path, request) - - static_path = os.path.join(os.path.dirname(__file__), "assets") - resource = CustomFileServer(static_path) - site = web_server.Site(resource) - reactor.listenTCP(self.PORT, site) + return + file_content = None + try: + file_content = open( + os.path.join(static_path, uri_path[1:]), "rb" + ).read() + except (FileNotFoundError, IsADirectoryError): + request.setResponseCode(HTTPStatus.NOT_FOUND) + if file_content: + request.setHeader("Content-Type", mimetypes.guess_type(uri_path)[0]) + if uri_path in gzip_routes: + request.setHeader("Content-Encoding", "gzip") + request.write(gzip.compress(file_content)) + else: + request.write(file_content) + self.setResponseCode(HTTPStatus.OK) + self.finish() + + class MyHttp(http.HTTPChannel): + requestFactory = TestServerHTTPHandler + + class MyHttpFactory(http.HTTPFactory): + protocol = MyHttp + + reactor.listenTCP(self.PORT, MyHttpFactory()) self.thread = threading.Thread( target=lambda: reactor.run(installSignalHandlers=0) ) @@ -114,9 +126,21 @@ def set_auth(self, path: str, username: str, password: str): def reset(self): self.request_subscribers.clear() self.auth.clear() + self.gzip_routes.clear() def set_route(self, path, callback): self.routes[path] = callback + def enable_gzip(self, path): + self.gzip_routes.add(path) + + def set_redirect(self, from_, to): + def handle_redirect(request): + request.setResponseCode(HTTPStatus.FOUND) + request.setHeader("location", to) + request.finish() + + self.set_route(from_, handle_redirect) + server = Server() diff --git a/tests/test_add_init_script.py b/tests/test_add_init_script.py index 9a9fe48ea..f1548952c 100644 --- a/tests/test_add_init_script.py +++ b/tests/test_add_init_script.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os + from playwright.helper import Error -from os import path async def test_add_init_script_evaluate_before_anything_else_on_the_page(page): @@ -24,7 +25,9 @@ async def test_add_init_script_evaluate_before_anything_else_on_the_page(page): async def test_add_init_script_work_with_a_path(page): await page.addInitScript( - path=path.join(path.dirname(path.abspath(__file__)), "assets/injectedfile.js") + path=os.path.join( + os.path.dirname(os.path.abspath(__file__)), "assets/injectedfile.js" + ) ) await page.goto("data:text/html,") assert await page.evaluate("window.result") == 123 @@ -57,7 +60,9 @@ async def test_add_init_script_work_with_browser_context_scripts_with_a_path( page, context ): await context.addInitScript( - path=path.join(path.dirname(path.abspath(__file__)), "assets/injectedfile.js") + path=os.path.join( + os.path.dirname(os.path.abspath(__file__)), "assets/injectedfile.js" + ) ) page = await context.newPage() await page.goto("data:text/html,") diff --git a/tests/test_download.py b/tests/test_download.py index f1ae32d90..11e16511b 100644 --- a/tests/test_download.py +++ b/tests/test_download.py @@ -13,13 +13,14 @@ # limitations under the License. from asyncio.futures import Future from typing import Optional -from playwright.browser import Browser -from playwright.page import Page -from playwright.helper import Error as PlaywrightError import pytest import asyncio import os +from playwright.browser import Browser +from playwright.page import Page +from playwright.helper import Error as PlaywrightError + def assert_file_content(path, content): with open(path, "r") as fd: @@ -29,14 +30,16 @@ def assert_file_content(path, content): @pytest.fixture(autouse=True) def after_each_hook(server): def handle_download(request): - request.setHeader(b"Content-Type", "application/octet-stream") - request.setHeader(b"Content-Disposition", "attachment") + request.setHeader("Content-Type", "application/octet-stream") + request.setHeader("Content-Disposition", "attachment") request.write(b"Hello world") + request.finish() def handle_download_with_file_name(request): - request.setHeader(b"Content-Type", "application/octet-stream") - request.setHeader(b"Content-Disposition", "attachment; filename=file.txt") + request.setHeader("Content-Type", "application/octet-stream") + request.setHeader("Content-Disposition", "attachment; filename=file.txt") request.write(b"Hello world") + request.finish() server.set_route("/download", handle_download) server.set_route("/downloadWithFilename", handle_download_with_file_name) @@ -73,8 +76,9 @@ async def test_should_report_downloads_with_acceptDownloads_true(browser, server async def test_should_report_non_navigation_downloads(browser, server): # Mac WebKit embedder does not download in this case, although Safari does. def handle_download(request): - request.setHeader(b"Content-Type", "application/octet-stream") + request.setHeader("Content-Type", "application/octet-stream") request.write(b"Hello world") + request.finish() server.set_route("/download", handle_download) @@ -135,8 +139,9 @@ async def test_should_report_alt_click_downloads(browser, server): # Firefox does not download on alt-click by default. # Our WebKit embedder does not download on alt-click, although Safari does. def handle_download(request): - request.setHeader(b"Content-Type", "application/octet-stream") + request.setHeader("Content-Type", "application/octet-stream") request.write(b"Hello world") + request.finish() server.set_route("/download", handle_download) diff --git a/tests/test_network.py b/tests/test_network.py index 93da9b9e4..f80d793f8 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -12,7 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from asyncio.futures import Future +import json import asyncio +import pytest +import os +from typing import Dict, List + +from playwright.network import Request +from playwright.page import Page +from playwright.helper import Error async def test_request_fulfill(page): @@ -55,3 +64,444 @@ async def handle_request(route, request, intercepted): assert response.ok assert intercepted == [True] assert await page.title() == "" + + +async def test_page_events_request_should_fire_for_navigation_requests( + page: Page, server +): + requests = [] + page.on("request", lambda r: requests.append(r)) + await page.goto(server.EMPTY_PAGE) + assert len(requests) == 1 + + +async def test_page_events_request_should_fire_for_iframes(page, server, utils): + requests = [] + page.on("request", lambda r: requests.append(r)) + await page.goto(server.EMPTY_PAGE) + await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) + assert len(requests) == 2 + + +async def test_page_events_request_should_fire_for_fetches(page, server): + requests = [] + page.on("request", lambda r: requests.append(r)) + await page.goto(server.EMPTY_PAGE) + await page.evaluate('() => fetch("/empty.html")') + assert len(requests) == 2 + + +async def test_page_events_request_should_report_requests_and_responses_handled_by_service_worker( + page: Page, server +): + await page.goto(server.PREFIX + "/serviceworkers/fetchdummy/sw.html") + await page.evaluate("() => window.activationPromise") + [sw_response, request] = await asyncio.gather( + page.evaluate('() => fetchDummy("foo")'), page.waitForEvent("request"), + ) + assert sw_response == "responseFromServiceWorker:foo" + assert request.url == server.PREFIX + "/serviceworkers/fetchdummy/foo" + response = await request.response + assert response.url == server.PREFIX + "/serviceworkers/fetchdummy/foo" + assert await response.text() == "responseFromServiceWorker:foo" + + +async def test_request_frame_should_work_for_main_frame_navigation_request( + page, server +): + requests = [] + page.on("request", lambda r: requests.append(r)) + await page.goto(server.EMPTY_PAGE) + assert len(requests) == 1 + assert requests[0].frame == page.mainFrame + + +async def test_request_frame_should_work_for_subframe_navigation_request( + page, server, utils +): + await page.goto(server.EMPTY_PAGE) + requests = [] + page.on("request", lambda r: requests.append(r)) + await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) + assert len(requests) == 1 + assert requests[0].frame == page.frames[1] + + +async def test_request_frame_should_work_for_fetch_requests(page, server): + await page.goto(server.EMPTY_PAGE) + requests: List[Request] = [] + page.on("request", lambda r: requests.append(r)) + await page.evaluate('() => fetch("/digits/1.png")') + requests = [r for r in requests if "favicon" not in r.url] + assert len(requests) == 1 + assert requests[0].frame == page.mainFrame + + +async def test_request_headers_should_work( + page, server, is_chromium, is_firefox, is_webkit +): + response = await page.goto(server.EMPTY_PAGE) + if is_chromium: + assert "Chrome" in response.request.headers["user-agent"] + elif is_firefox: + assert "Firefox" in response.request.headers["user-agent"] + elif is_webkit: + assert "WebKit" in response.request.headers["user-agent"] + + +@pytest.mark.only_browser("firefox") +async def test_request_headers_should_get_the_same_headers_as_the_server( + page: Page, server +): + await page.goto(server.PREFIX + "/empty.html") + server_request_headers_future: Future[Dict[str, str]] = asyncio.Future() + + def handle_something(request): + normalized_headers = { + key.decode().lower(): value[0].decode() + for key, value in request.requestHeaders.getAllRawHeaders() + } + server_request_headers_future.set_result(normalized_headers) + request.setHeader("Access-Control-Allow-Origin", "*") + request.write(b"done") + request.finish() + + server.set_route("/something", handle_something) + + requestPromise = asyncio.ensure_future(page.waitForEvent("request")) + text = await page.evaluate( + """async url => { + const data = await fetch(url); + return data.text(); + }""", + server.CROSS_PROCESS_PREFIX + "/something", + ) + request: Request = await requestPromise + assert text == "done" + server_headers = await server_request_headers_future + assert request.headers == server_headers + + +async def test_response_headers_should_work(page, server): + server.set_route("/empty.html", lambda r: (r.setHeader("foo", "bar"), r.finish())) + + response = await page.goto(server.EMPTY_PAGE) + assert response.headers["foo"] == "bar" + + +async def test_request_postdata_should_work(page, server): + await page.goto(server.EMPTY_PAGE) + server.set_route("/post", lambda r: r.finish()) + requests = [] + page.on("request", lambda r: requests.append(r)) + await page.evaluate( + '() => fetch("./post", { method: "POST", body: JSON.stringify({foo: "bar"})})' + ) + assert len(requests) == 1 + assert requests[0].postData == '{"foo":"bar"}' + + +async def test_request_postdata_should_be_undefined_when_there_is_no_post_data( + page, server +): + response = await page.goto(server.EMPTY_PAGE) + assert response.request.postData is None + + +async def test_response_text_should_work(page, server): + response = await page.goto(server.PREFIX + "/simple.json") + assert await response.text() == '{"foo": "bar"}\n' + + +async def test_response_text_should_return_uncompressed_text(page, server): + server.enable_gzip("/simple.json") + response = await page.goto(server.PREFIX + "/simple.json") + assert response.headers["content-encoding"] == "gzip" + assert await response.text() == '{"foo": "bar"}\n' + + +async def test_response_text_should_throw_when_requesting_body_of_redirected_response( + page, server +): + server.set_redirect("/foo.html", "/empty.html") + response = await page.goto(server.PREFIX + "/foo.html") + redirectedFrom = response.request.redirectedFrom + assert redirectedFrom + redirected = await redirectedFrom.response + assert redirected.status == 302 + error = None + try: + await redirected.text() + except Error as exc: + error = exc + assert "Response body is unavailable for redirect responses" in error.message + + +async def test_response_json_should_work(page, server): + response = await page.goto(server.PREFIX + "/simple.json") + assert await response.json() == {"foo": "bar"} + + +async def test_response_body_should_work(page, server): + response = await page.goto(server.PREFIX + "/pptr.png") + with open( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets/pptr.png"), + "rb", + ) as fd: + assert fd.read() == await response.body() + + +async def test_response_body_should_work_with_compression(page, server): + server.enable_gzip("/pptr.png") + response = await page.goto(server.PREFIX + "/pptr.png") + with open( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets/pptr.png"), + "rb", + ) as fd: + assert fd.read() == await response.body() + + +async def test_response_status_text_should_work(page, server): + server.set_route("/cool", lambda r: (r.setResponseCode(200, b"cool!"), r.finish())) + + response = await page.goto(server.PREFIX + "/cool") + assert response.statusText == "cool!" + + +async def test_request_resource_type_should_return_event_source(page, server): + SSE_MESSAGE = {"foo": "bar"} + # 1. Setup server-sent events on server that immediately sends a message to the client. + server.set_route( + "/sse", + lambda r: ( + r.setHeader("Content-Type", "text/event-stream"), + r.setHeader("Connection", "keep-alive"), + r.setHeader("Cache-Control", "no-cache"), + r.setResponseCode(200), + r.write(f"data: {json.dumps(SSE_MESSAGE)}\n\n".encode()), + r.finish(), + ), + ) + + # 2. Subscribe to page request events. + await page.goto(server.EMPTY_PAGE) + requests = [] + page.on("request", lambda r: requests.append(r)) + # 3. Connect to EventSource in browser and return first message. + assert ( + await page.evaluate( + """() => { + const eventSource = new EventSource('/sse'); + return new Promise(resolve => { + eventSource.onmessage = e => resolve(JSON.parse(e.data)); + }); + }""" + ) + == SSE_MESSAGE + ) + assert requests[0].resourceType == "eventsource" + + +async def test_network_events_request(page, server): + requests = [] + page.on("request", lambda r: requests.append(r)) + await page.goto(server.EMPTY_PAGE) + assert len(requests) == 1 + assert requests[0].url == server.EMPTY_PAGE + assert requests[0].resourceType == "document" + assert requests[0].method == "GET" + assert await requests[0].response + assert requests[0].frame == page.mainFrame + assert requests[0].frame.url == server.EMPTY_PAGE + + +async def test_network_events_response(page, server): + responses = [] + page.on("response", lambda r: responses.append(r)) + await page.goto(server.EMPTY_PAGE) + assert len(responses) == 1 + assert responses[0].url == server.EMPTY_PAGE + assert responses[0].status == 200 + assert responses[0].ok + assert responses[0].request + + +async def test_network_events_request_failed( + page, server, is_chromium, is_webkit, is_firefox, is_mac, is_win +): + def handle_request(request): + request.setHeader("Content-Type", "text/css") + request.transport.protocol.transport.socket.close() + + server.set_route("/one-style.css", handle_request) + + failed_requests = [] + page.on("requestfailed", lambda request: failed_requests.append(request)) + await page.goto(server.PREFIX + "/one-style.html") + assert len(failed_requests) == 1 + assert "one-style.css" in failed_requests[0].url + assert await failed_requests[0].response is None + assert failed_requests[0].resourceType == "stylesheet" + if is_chromium: + assert failed_requests[0].failure == "net::ERR_EMPTY_RESPONSE" + elif is_webkit: + if is_mac: + assert failed_requests[0].failure == "The network connection was lost." + elif is_win: + assert ( + failed_requests[0].failure + == "Server returned nothing (no headers, no data)" + ) + else: + assert failed_requests[0].failure == "Message Corrupt" + else: + assert failed_requests[0].failure == "NS_ERROR_NET_RESET" + assert failed_requests[0].frame + + +async def test_network_events_request_finished(page, server): + response = ( + await asyncio.gather( + page.goto(server.EMPTY_PAGE), page.waitForEvent("requestfinished") + ) + )[0] + request = response.request + assert request.url == server.EMPTY_PAGE + assert await request.response + assert request.frame == page.mainFrame + assert request.frame.url == server.EMPTY_PAGE + + +async def test_network_events_should_fire_events_in_proper_order(page, server): + events = [] + page.on("request", lambda request: events.append("request")) + page.on("response", lambda response: events.append("response")) + response = await page.goto(server.EMPTY_PAGE) + await response.finished() + events.append("requestfinished") + assert events == ["request", "response", "requestfinished"] + + +async def test_network_events_should_support_redirects(page, server): + events = [] + page.on("request", lambda request: events.append(f"{request.method} {request.url}")) + page.on( + "response", lambda response: events.append(f"{response.status} {response.url}") + ) + page.on("requestfinished", lambda request: events.append(f"DONE {request.url}")) + page.on("requestfailed", lambda request: events.append(f"FAIL {request.url}")) + server.set_redirect("/foo.html", "/empty.html") + FOO_URL = server.PREFIX + "/foo.html" + response = await page.goto(FOO_URL) + await response.finished() + assert events == [ + f"GET {FOO_URL}", + f"302 {FOO_URL}", + f"DONE {FOO_URL}", + f"GET {server.EMPTY_PAGE}", + f"200 {server.EMPTY_PAGE}", + f"DONE {server.EMPTY_PAGE}", + ] + redirectedFrom = response.request.redirectedFrom + assert "/foo.html" in redirectedFrom.url + assert redirectedFrom.redirectedFrom is None + assert redirectedFrom.redirectedTo == response.request + + +async def test_request_is_navigation_request_should_work(page, server): + pytest.skip(msg="test") + requests = {} + + def handle_request(request): + requests[request.url().split("/").pop()] = request + + page.on("request", handle_request) + server.set_redirect("/rrredirect", "/frames/one-frame.html") + await page.goto(server.PREFIX + "/rrredirect") + print("kek") + assert requests.get("rrredirect").isNavigationRequest + assert requests.get("one-frame.html").isNavigationRequest + assert requests.get("frame.html").isNavigationRequest + assert requests.get("script.js").isNavigationRequest is False + assert requests.get("style.css").isNavigationRequest is False + + +async def test_request_is_navigation_request_should_work_when_navigating_to_image( + page, server +): + requests = [] + page.on("request", lambda r: requests.append(r)) + await page.goto(server.PREFIX + "/pptr.png") + assert requests[0].isNavigationRequest() + + +async def test_set_extra_http_headers_should_work(page, server): + await page.setExtraHTTPHeaders({"foo": "bar"}) + + request = ( + await asyncio.gather( + server.wait_for_request("/empty.html"), page.goto(server.EMPTY_PAGE), + ) + )[0] + assert request.getHeader("foo") == "bar" + + +async def test_set_extra_http_headers_should_work_with_redirects(page, server): + server.set_redirect("/foo.html", "/empty.html") + await page.setExtraHTTPHeaders({"foo": "bar"}) + + request = ( + await asyncio.gather( + server.wait_for_request("/empty.html"), + page.goto(server.PREFIX + "/foo.html"), + ) + )[0] + assert request.getHeader("foo") == "bar" + + +async def test_set_extra_http_headers_should_work_with_extra_headers_from_browser_context( + browser, server +): + context = await browser.newContext() + await context.setExtraHTTPHeaders({"foo": "bar"}) + + page = await context.newPage() + request = ( + await asyncio.gather( + server.wait_for_request("/empty.html"), page.goto(server.EMPTY_PAGE), + ) + )[0] + await context.close() + assert request.getHeader("foo") == "bar" + + +async def test_set_extra_http_headers_should_override_extra_headers_from_browser_context( + browser, server +): + context = await browser.newContext(extraHTTPHeaders={"fOo": "bAr", "baR": "foO"}) + + page = await context.newPage() + await page.setExtraHTTPHeaders({"Foo": "Bar"}) + + request = ( + await asyncio.gather( + server.wait_for_request("/empty.html"), page.goto(server.EMPTY_PAGE), + ) + )[0] + await context.close() + assert request.getHeader("foo") == "Bar" + assert request.getHeader("bar") == "foO" + + +async def test_set_extra_http_headers_should_throw_for_non_string_header_values( + page, server +): + error = None + try: + await page.setExtraHTTPHeaders({"foo": 1}) + except Error as exc: + error = exc + assert ( + error.message + == 'Expected value of header "foo" to be String, but "number" is found.' + ) From 481e54099aaa1fc756f766d8b5f6b7c0f1af49f1 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 10:19:27 +0200 Subject: [PATCH 2/9] ci: run tests in debug mode --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc471d4e2..324a74bd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,4 +51,4 @@ jobs: - name: Build package run: python build_package.py - name: Test - run: pytest --browser=${{ matrix.browser }} -n auto + run: pytest -vv --browser=${{ matrix.browser }} -n auto From 0ae5bf077faa3f96314340686ee1d4a3288a48b2 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 10:50:26 +0200 Subject: [PATCH 3/9] fix: lose connection test --- tests/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_network.py b/tests/test_network.py index f80d793f8..99dc0a9a4 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -331,7 +331,7 @@ async def test_network_events_request_failed( ): def handle_request(request): request.setHeader("Content-Type", "text/css") - request.transport.protocol.transport.socket.close() + request.transport.loseConnection() server.set_route("/one-style.css", handle_request) From c21ac307f9af5d9221aed238680b0d54a259840f Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 12:51:56 +0200 Subject: [PATCH 4/9] ci: added max timeout of 30 minutes --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 324a74bd6..6da458831 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: branches: [ master ] jobs: build: + timeout-minutes: 30 strategy: fail-fast: false matrix: From 65ff72ff08488eda54ad85fc7aeaecbd0bbd223c Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 13:09:45 +0200 Subject: [PATCH 5/9] ci: removed concurrency --- .gitattributes | 3 +++ .github/workflows/ci.yml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..03d42ee97 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# text files must be lf for golden file tests to work +*.txt eol=lf +*.json eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6da458831..c954530f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,4 +52,4 @@ jobs: - name: Build package run: python build_package.py - name: Test - run: pytest -vv --browser=${{ matrix.browser }} -n auto + run: pytest -vv --browser=${{ matrix.browser }} From 97ea6151de68cded37da1bc14bd993318b64b156 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 13:45:41 +0200 Subject: [PATCH 6/9] try to fix flaky test --- tests/test_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_network.py b/tests/test_network.py index 99dc0a9a4..d6bc46e61 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -96,8 +96,8 @@ async def test_page_events_request_should_report_requests_and_responses_handled_ ): await page.goto(server.PREFIX + "/serviceworkers/fetchdummy/sw.html") await page.evaluate("() => window.activationPromise") - [sw_response, request] = await asyncio.gather( - page.evaluate('() => fetchDummy("foo")'), page.waitForEvent("request"), + [request, sw_response] = await asyncio.gather( + page.waitForEvent("request"), page.evaluate('() => fetchDummy("foo")') ) assert sw_response == "responseFromServiceWorker:foo" assert request.url == server.PREFIX + "/serviceworkers/fetchdummy/foo" From 4871a46f7f8808e8dd8806ddbb631caafed81701 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 18:20:11 +0200 Subject: [PATCH 7/9] fix: skip failing test on windows --- tests/conftest.py | 41 +++++++++++++++++++++++++++++------------ tests/test_network.py | 1 + 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d1b610f49..cd0ad3a82 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -129,23 +129,40 @@ def is_mac(browser_name): return sys.platform == "darwin" -@pytest.fixture(autouse=True) -def skip_by_browser(request, browser_name): - skip_browsers_names = [] - +def _get_skiplist(request, values, value_name): + skipped_values = [] # Allowlist - only_browser_marker = request.node.get_closest_marker("only_browser") - if only_browser_marker: - skip_browsers_names = ["chromium", "firefox", "webkit"] - skip_browsers_names.remove(only_browser_marker.args[0]) + only_marker = request.node.get_closest_marker(f"only_{value_name}") + if only_marker: + skipped_values = values + skipped_values.remove(only_marker.args[0]) # Denylist - skip_browser_marker = request.node.get_closest_marker("skip_browser") - if skip_browser_marker: - skip_browsers_names.append(skip_browser_marker.args[0]) + skip_marker = request.node.get_closest_marker(f"skip_{value_name}") + if skip_marker: + skipped_values.append(skip_marker.args[0]) + + return skipped_values + + +@pytest.fixture(autouse=True) +def skip_by_browser(request, browser_name): + skip_browsers_names = _get_skiplist( + request, ["chromium", "firefox", "webkit"], "browser" + ) if browser_name in skip_browsers_names: - pytest.skip("skipped on this platform: {}".format(browser_name)) + pytest.skip("skipped for this browser: {}".format(browser_name)) + + +@pytest.fixture(autouse=True) +def skip_by_platform(request): + skip_platform_names = _get_skiplist( + request, ["win32", "linux", "darwin"], "platform" + ) + + if sys.platform in skip_platform_names: + pytest.skip("skipped on this platform: {}".format(sys.platform)) def pytest_addoption(parser): diff --git a/tests/test_network.py b/tests/test_network.py index d6bc46e61..39d7b6e03 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -91,6 +91,7 @@ async def test_page_events_request_should_fire_for_fetches(page, server): assert len(requests) == 2 +@pytest.mark.skip_platform("win32") # TODO: needs to be investigated async def test_page_events_request_should_report_requests_and_responses_handled_by_service_worker( page: Page, server ): From d0ffe3a25cf9d3d15212fb495bbdd70063467c0e Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 19:43:44 +0200 Subject: [PATCH 8/9] fix: failed tests --- setup.cfg | 2 ++ tests/test_page.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f2755ba11..479f99f5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,6 +2,8 @@ markers = skip_browser only_browser + skip_platform + only_platform [mypy] ignore_missing_imports = True [flake8] diff --git a/tests/test_page.py b/tests/test_page.py index dfcf5cbaf..dacc98722 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -502,7 +502,7 @@ async def test_set_content_should_respect_default_navigation_timeout(page, serve async def test_set_content_should_await_resources_to_load(page, server): img_path = "/img.png" - img_route = page._scope._loop.create_future() + img_route = asyncio.Future() await page.route(img_path, lambda route, request: img_route.set_result(route)) loaded = [] From 09cdd0d62bae4aaee4c369dd787735d1efe211a7 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Sun, 12 Jul 2020 20:19:40 +0200 Subject: [PATCH 9/9] fix: tests --- tests/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/server.py b/tests/server.py index eb03a3110..4b74806c6 100644 --- a/tests/server.py +++ b/tests/server.py @@ -127,6 +127,7 @@ def reset(self): self.request_subscribers.clear() self.auth.clear() self.gzip_routes.clear() + self.routes.clear() def set_route(self, path, callback): self.routes[path] = callback