# 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.

import asyncio
import re
import sys
from pathlib import Path
from typing import Any, List, Optional

import pytest

from playwright.async_api import (
    BrowserContext,
    Error,
    Page,
    Request,
    Response,
    Route,
    TimeoutError,
)
from tests.server import Server, TestServerRequest


async def test_goto_should_work(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE)
    assert page.url == server.EMPTY_PAGE


async def test_goto_should_work_with_file_URL(page: Page, assetdir: Path) -> None:
    fileurl = (assetdir / "frames" / "two-frames.html").as_uri()
    await page.goto(fileurl)
    assert page.url.lower() == fileurl.lower()
    assert len(page.frames) == 3


async def test_goto_should_use_http_for_no_protocol(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE[7:])
    assert page.url == server.EMPTY_PAGE


async def test_goto_should_work_cross_process(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE)
    assert page.url == server.EMPTY_PAGE

    url = server.CROSS_PROCESS_PREFIX + "/empty.html"
    request_frames = []

    def on_request(r: Request) -> None:
        if r.url == url:
            request_frames.append(r.frame)

    page.on("request", on_request)

    response = await page.goto(url)
    assert response
    assert page.url == url
    assert response.frame == page.main_frame
    assert request_frames[0] == page.main_frame
    assert response.url == url


async def test_goto_should_capture_iframe_navigation_request(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    assert page.url == server.EMPTY_PAGE

    request_frames = []

    def on_request(r: Request) -> None:
        if r.url == server.PREFIX + "/frames/frame.html":
            request_frames.append(r.frame)

    page.on("request", on_request)

    response = await page.goto(server.PREFIX + "/frames/one-frame.html")
    assert response
    assert page.url == server.PREFIX + "/frames/one-frame.html"
    assert response.frame == page.main_frame
    assert response.url == server.PREFIX + "/frames/one-frame.html"

    assert len(page.frames) == 2
    assert request_frames[0] == page.frames[1]


async def test_goto_should_capture_cross_process_iframe_navigation_request(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    assert page.url == server.EMPTY_PAGE

    request_frames = []

    def on_request(r: Request) -> None:
        if r.url == server.CROSS_PROCESS_PREFIX + "/frames/frame.html":
            request_frames.append(r.frame)

    page.on("request", on_request)

    response = await page.goto(server.CROSS_PROCESS_PREFIX + "/frames/one-frame.html")
    assert response
    assert page.url == server.CROSS_PROCESS_PREFIX + "/frames/one-frame.html"
    assert response.frame == page.main_frame
    assert response.url == server.CROSS_PROCESS_PREFIX + "/frames/one-frame.html"

    assert len(page.frames) == 2
    assert request_frames[0] == page.frames[1]


async def test_goto_should_work_with_anchor_navigation(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    assert page.url == server.EMPTY_PAGE
    await page.goto(server.EMPTY_PAGE + "#foo")
    assert page.url == server.EMPTY_PAGE + "#foo"
    await page.goto(server.EMPTY_PAGE + "#bar")
    assert page.url == server.EMPTY_PAGE + "#bar"


async def test_goto_should_work_with_redirects(page: Page, server: Server) -> None:
    server.set_redirect("/redirect/1.html", "/redirect/2.html")
    server.set_redirect("/redirect/2.html", "/empty.html")
    response = await page.goto(server.PREFIX + "/redirect/1.html")
    assert response
    assert response.status == 200
    assert page.url == server.EMPTY_PAGE


async def test_goto_should_navigate_to_about_blank(page: Page, server: Server) -> None:
    response = await page.goto("about:blank")
    assert response is None


async def test_goto_should_return_response_when_page_changes_its_url_after_load(
    page: Page, server: Server
) -> None:
    response = await page.goto(server.PREFIX + "/historyapi.html")
    assert response
    assert response.status == 200


@pytest.mark.skip_browser("firefox")
async def test_goto_should_work_with_subframes_return_204(
    page: Page, server: Server
) -> None:
    def handle(request: TestServerRequest) -> None:
        request.setResponseCode(204)
        request.finish()

    server.set_route("/frames/frame.html", handle)

    await page.goto(server.PREFIX + "/frames/one-frame.html")


async def test_goto_should_fail_when_server_returns_204(
    page: Page, server: Server, is_chromium: bool, is_webkit: bool
) -> None:
    # WebKit just loads an empty page.
    def handle(request: TestServerRequest) -> None:
        request.setResponseCode(204)
        request.finish()

    server.set_route("/empty.html", handle)

    with pytest.raises(Error) as exc_info:
        await page.goto(server.EMPTY_PAGE)
    assert exc_info.value
    if is_chromium:
        assert "net::ERR_ABORTED" in exc_info.value.message
    elif is_webkit:
        assert "Aborted: 204 No Content" in exc_info.value.message
    else:
        assert "NS_BINDING_ABORTED" in exc_info.value.message


async def test_goto_should_navigate_to_empty_page_with_domcontentloaded(
    page: Page, server: Server
) -> None:
    response = await page.goto(server.EMPTY_PAGE, wait_until="domcontentloaded")
    assert response
    assert response.status == 200


async def test_goto_should_work_when_page_calls_history_api_in_beforeunload(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.evaluate(
        """() => {
        window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false)
    }"""
    )

    response = await page.goto(server.PREFIX + "/grid.html")
    assert response
    assert response.status == 200


async def test_goto_should_fail_when_navigating_to_bad_url(
    page: Page, is_chromium: bool, is_webkit: bool
) -> None:
    with pytest.raises(Error) as exc_info:
        await page.goto("asdfasdf")
    if is_chromium or is_webkit:
        assert "Cannot navigate to invalid URL" in exc_info.value.message
    else:
        assert "Invalid url" in exc_info.value.message


async def test_goto_should_fail_when_navigating_to_bad_ssl(
    page: Page, https_server: Server, browser_name: str
) -> None:
    with pytest.raises(Error) as exc_info:
        await page.goto(https_server.EMPTY_PAGE)
    expect_ssl_error(exc_info.value.message, browser_name)


async def test_goto_should_fail_when_navigating_to_bad_ssl_after_redirects(
    page: Page, server: Server, https_server: Server, browser_name: str
) -> None:
    server.set_redirect("/redirect/1.html", "/redirect/2.html")
    server.set_redirect("/redirect/2.html", "/empty.html")
    with pytest.raises(Error) as exc_info:
        await page.goto(https_server.PREFIX + "/redirect/1.html")
    expect_ssl_error(exc_info.value.message, browser_name)


async def test_goto_should_not_crash_when_navigating_to_bad_ssl_after_a_cross_origin_navigation(
    page: Page, server: Server, https_server: Server
) -> None:
    await page.goto(server.CROSS_PROCESS_PREFIX + "/empty.html")
    with pytest.raises(Error):
        await page.goto(https_server.EMPTY_PAGE)


async def test_goto_should_throw_if_networkidle2_is_passed_as_an_option(
    page: Page, server: Server
) -> None:
    with pytest.raises(Error) as exc_info:
        await page.goto(server.EMPTY_PAGE, wait_until="networkidle2")  # type: ignore
    assert (
        "wait_until: expected one of (load|domcontentloaded|networkidle|commit)"
        in exc_info.value.message
    )


async def test_goto_should_fail_when_main_resources_failed_to_load(
    page: Page, is_chromium: bool, is_webkit: bool, is_win: bool
) -> None:
    with pytest.raises(Error) as exc_info:
        await page.goto("http://localhost:44123/non-existing-url")
    if is_chromium:
        assert "net::ERR_CONNECTION_REFUSED" in exc_info.value.message
    elif is_webkit and is_win:
        assert "Could not connect to server" in exc_info.value.message
    elif is_webkit:
        assert "Could not connect" in exc_info.value.message
    else:
        assert "NS_ERROR_CONNECTION_REFUSED" in exc_info.value.message


async def test_goto_should_fail_when_exceeding_maximum_navigation_timeout(
    page: Page, server: Server
) -> None:
    # Hang for request to the empty.html
    server.set_route("/empty.html", lambda request: None)
    with pytest.raises(Error) as exc_info:
        await page.goto(server.PREFIX + "/empty.html", timeout=1)
    assert "Timeout 1ms exceeded" in exc_info.value.message
    assert server.PREFIX + "/empty.html" in exc_info.value.message
    assert isinstance(exc_info.value, TimeoutError)


async def test_goto_should_fail_when_exceeding_default_maximum_navigation_timeout(
    page: Page, server: Server
) -> None:
    # Hang for request to the empty.html
    server.set_route("/empty.html", lambda request: None)
    page.context.set_default_navigation_timeout(2)
    page.set_default_navigation_timeout(1)
    with pytest.raises(Error) as exc_info:
        await page.goto(server.PREFIX + "/empty.html")
    assert "Timeout 1ms exceeded" in exc_info.value.message
    assert server.PREFIX + "/empty.html" in exc_info.value.message
    assert isinstance(exc_info.value, TimeoutError)


async def test_goto_should_fail_when_exceeding_browser_context_navigation_timeout(
    page: Page, server: Server
) -> None:
    # Hang for request to the empty.html
    server.set_route("/empty.html", lambda request: None)
    page.context.set_default_navigation_timeout(2)
    with pytest.raises(Error) as exc_info:
        await page.goto(server.PREFIX + "/empty.html")
    assert "Timeout 2ms exceeded" in exc_info.value.message
    assert server.PREFIX + "/empty.html" in exc_info.value.message
    assert isinstance(exc_info.value, TimeoutError)


async def test_goto_should_fail_when_exceeding_default_maximum_timeout(
    page: Page, server: Server
) -> None:
    # Hang for request to the empty.html
    server.set_route("/empty.html", lambda request: None)
    page.context.set_default_timeout(2)
    page.set_default_timeout(1)
    with pytest.raises(Error) as exc_info:
        await page.goto(server.PREFIX + "/empty.html")
    assert "Timeout 1ms exceeded" in exc_info.value.message
    assert server.PREFIX + "/empty.html" in exc_info.value.message
    assert isinstance(exc_info.value, TimeoutError)


async def test_goto_should_fail_when_exceeding_browser_context_timeout(
    page: Page, server: Server
) -> None:
    # Hang for request to the empty.html
    server.set_route("/empty.html", lambda request: None)
    page.context.set_default_timeout(2)
    with pytest.raises(Error) as exc_info:
        await page.goto(server.PREFIX + "/empty.html")
    assert "Timeout 2ms exceeded" in exc_info.value.message
    assert server.PREFIX + "/empty.html" in exc_info.value.message
    assert isinstance(exc_info.value, TimeoutError)


async def test_goto_should_prioritize_default_navigation_timeout_over_default_timeout(
    page: Page, server: Server
) -> None:
    # Hang for request to the empty.html
    server.set_route("/empty.html", lambda request: None)
    page.set_default_timeout(0)
    page.set_default_navigation_timeout(1)
    with pytest.raises(Error) as exc_info:
        await page.goto(server.PREFIX + "/empty.html")
    assert "Timeout 1ms exceeded" in exc_info.value.message
    assert server.PREFIX + "/empty.html" in exc_info.value.message
    assert isinstance(exc_info.value, TimeoutError)


async def test_goto_should_disable_timeout_when_its_set_to_0(
    page: Page, server: Server
) -> None:
    loaded: List[bool] = []
    page.once("load", lambda _: loaded.append(True))
    await page.goto(server.PREFIX + "/grid.html", timeout=0, wait_until="load")
    assert loaded == [True]


async def test_goto_should_work_when_navigating_to_valid_url(
    page: Page, server: Server
) -> None:
    response = await page.goto(server.EMPTY_PAGE)
    assert response
    assert response.ok


async def test_goto_should_work_when_navigating_to_data_url(
    page: Page, server: Server
) -> None:
    response = await page.goto("data:text/html,hello")
    assert response is None


async def test_goto_should_work_when_navigating_to_404(
    page: Page, server: Server
) -> None:
    response = await page.goto(server.PREFIX + "/not-found")
    assert response
    assert response.ok is False
    assert response.status == 404


async def test_goto_should_return_last_response_in_redirect_chain(
    page: Page, server: Server
) -> None:
    server.set_redirect("/redirect/1.html", "/redirect/2.html")
    server.set_redirect("/redirect/2.html", "/redirect/3.html")
    server.set_redirect("/redirect/3.html", server.EMPTY_PAGE)
    response = await page.goto(server.PREFIX + "/redirect/1.html")
    assert response
    assert response.ok
    assert response.url == server.EMPTY_PAGE


async def test_goto_should_navigate_to_data_url_and_not_fire_dataURL_requests(
    page: Page, server: Server
) -> None:
    requests = []
    page.on("request", lambda request: requests.append(request))
    dataURL = "data:text/html,<div>yo</div>"
    response = await page.goto(dataURL)
    assert response is None
    assert requests == []


async def test_goto_should_navigate_to_url_with_hash_and_fire_requests_without_hash(
    page: Page, server: Server
) -> None:
    requests = []
    page.on("request", lambda request: requests.append(request))
    response = await page.goto(server.EMPTY_PAGE + "#hash")
    assert response
    assert response.status == 200
    assert response.url == server.EMPTY_PAGE
    assert len(requests) == 1
    assert requests[0].url == server.EMPTY_PAGE


async def test_goto_should_work_with_self_requesting_page(
    page: Page, server: Server
) -> None:
    response = await page.goto(server.PREFIX + "/self-request.html")
    assert response
    assert response.status == 200
    assert "self-request.html" in response.url


async def test_goto_should_fail_when_navigating_and_show_the_url_at_the_error_message(
    page: Page, https_server: Server
) -> None:
    url = https_server.PREFIX + "/redirect/1.html"
    with pytest.raises(Error) as exc_info:
        await page.goto(url)
    assert url in exc_info.value.message


async def test_goto_should_be_able_to_navigate_to_a_page_controlled_by_service_worker(
    page: Page, server: Server
) -> None:
    await page.goto(server.PREFIX + "/serviceworkers/fetch/sw.html")
    await page.evaluate("window.activationPromise")
    await page.goto(server.PREFIX + "/serviceworkers/fetch/sw.html")


async def test_goto_should_send_referer(page: Page, server: Server) -> None:
    [request1, request2, _] = await asyncio.gather(
        server.wait_for_request("/grid.html"),
        server.wait_for_request("/digits/1.png"),
        page.goto(server.PREFIX + "/grid.html", referer="http://google.com/"),
    )
    assert request1.getHeader("referer") == "http://google.com/"
    # Make sure subresources do not inherit referer.
    assert request2.getHeader("referer") == server.PREFIX + "/grid.html"
    assert page.url == server.PREFIX + "/grid.html"


async def test_goto_should_reject_referer_option_when_set_extra_http_headers_provides_referer(
    page: Page, server: Server
) -> None:
    await page.set_extra_http_headers({"referer": "http://microsoft.com/"})
    with pytest.raises(Error) as exc_info:
        await page.goto(server.PREFIX + "/grid.html", referer="http://google.com/")
    assert (
        '"referer" is already specified as extra HTTP header' in exc_info.value.message
    )
    assert server.PREFIX + "/grid.html" in exc_info.value.message


async def test_goto_should_work_with_commit(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE, wait_until="commit")
    assert page.url == server.EMPTY_PAGE


async def test_network_idle_should_navigate_to_empty_page_with_networkidle(
    page: Page, server: Server
) -> None:
    response = await page.goto(server.EMPTY_PAGE, wait_until="networkidle")
    assert response
    assert response.status == 200


async def test_wait_for_nav_should_work(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE)
    async with page.expect_navigation() as response_info:
        await page.evaluate(
            "url => window.location.href = url", server.PREFIX + "/grid.html"
        )
    response = await response_info.value
    assert response.ok
    assert "grid.html" in response.url


async def test_wait_for_nav_should_respect_timeout(page: Page, server: Server) -> None:
    with pytest.raises(Error) as exc_info:
        async with page.expect_navigation(url="**/frame.html", timeout=2500):
            await page.goto(server.EMPTY_PAGE)
    assert "Timeout 2500ms exceeded" in exc_info.value.message


async def test_wait_for_nav_should_work_with_both_domcontentloaded_and_load(
    page: Page, server: Server
) -> None:
    async with (
        page.expect_navigation(wait_until="domcontentloaded"),
        page.expect_navigation(wait_until="load"),
    ):
        await page.goto(server.PREFIX + "/one-style.html")


async def test_wait_for_nav_should_work_with_clicking_on_anchor_links(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.set_content('<a href="#foobar">foobar</a>')
    async with page.expect_navigation() as response_info:
        await page.click("a")
    response = await response_info.value
    assert response is None
    assert page.url == server.EMPTY_PAGE + "#foobar"


async def test_wait_for_nav_should_work_with_clicking_on_links_which_do_not_commit_navigation(
    page: Page, server: Server, https_server: Server, browser_name: str
) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.set_content(f"<a href='{https_server.EMPTY_PAGE}'>foobar</a>")
    with pytest.raises(Error) as exc_info:
        async with page.expect_navigation():
            await page.click("a")
    expect_ssl_error(exc_info.value.message, browser_name)


async def test_wait_for_nav_should_work_with_history_push_state(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.set_content(
        """
        <a onclick='javascript:pushState()'>SPA</a>
        <script>
            function pushState() { history.pushState({}, '', 'wow.html') }
        </script>
    """
    )
    async with page.expect_navigation() as response_info:
        await page.click("a")
    response = await response_info.value
    assert response is None
    assert page.url == server.PREFIX + "/wow.html"


async def test_wait_for_nav_should_work_with_history_replace_state(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.set_content(
        """
        <a onclick='javascript:replaceState()'>SPA</a>
        <script>
            function replaceState() { history.replaceState({}, '', '/replaced.html') }
        </script>
    """
    )
    async with page.expect_navigation() as response_info:
        await page.click("a")
    response = await response_info.value
    assert response is None
    assert page.url == server.PREFIX + "/replaced.html"


async def test_wait_for_nav_should_work_with_dom_history_back_forward(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.set_content(
        """
      <a id=back onclick='javascript:go_back()'>back</a>
      <a id=forward onclick='javascript:go_forward()'>forward</a>
      <script>
        function go_back() { history.back(); }
        function go_forward() { history.forward(); }
        history.pushState({}, '', '/first.html')
        history.pushState({}, '', '/second.html')
      </script>
    """
    )
    assert page.url == server.PREFIX + "/second.html"
    async with page.expect_navigation() as back_response_info:
        await page.click("a#back")
    back_response = await back_response_info.value
    assert back_response is None
    assert page.url == server.PREFIX + "/first.html"
    async with page.expect_navigation() as forward_response_info:
        await page.click("a#forward")
    forward_response = await forward_response_info.value
    assert forward_response is None
    assert page.url == server.PREFIX + "/second.html"


@pytest.mark.skip_browser(
    "webkit"
)  # WebKit issues load event in some cases, but not always
async def test_wait_for_nav_should_work_when_subframe_issues_window_stop(
    page: Page, server: Server, is_webkit: bool
) -> None:
    server.set_route("/frames/style.css", lambda _: None)
    done = False

    async def nav_and_mark_done() -> None:
        nonlocal done
        await page.goto(server.PREFIX + "/frames/one-frame.html")
        done = True

    task = asyncio.create_task(nav_and_mark_done())
    await asyncio.sleep(0)
    async with page.expect_event("frameattached") as frame_info:
        pass
    frame = await frame_info.value

    async with page.expect_event("framenavigated", lambda f: f == frame):
        pass
    await frame.evaluate("() => window.stop()")
    await page.wait_for_timeout(2000)  # give it some time to erroneously resolve
    assert done == (
        not is_webkit
    )  # Chromium and Firefox issue load event in this case.
    if is_webkit:
        task.cancel()


async def test_wait_for_nav_should_work_with_url_match(
    page: Page, server: Server
) -> None:
    responses: List[Optional[Response]] = [None, None, None]

    async def wait_for_nav(url: Any, index: int) -> None:
        async with page.expect_navigation(url=url) as response_info:
            pass
        responses[index] = await response_info.value

    response0_promise = asyncio.create_task(
        wait_for_nav(re.compile(r"one-style\.html"), 0)
    )
    response1_promise = asyncio.create_task(
        wait_for_nav(re.compile(r"\/frame.html"), 1)
    )
    response2_promise = asyncio.create_task(
        wait_for_nav(lambda url: "foo=bar" in url, 2)
    )
    assert responses == [None, None, None]
    await page.goto(server.EMPTY_PAGE)
    assert responses == [None, None, None]
    await page.goto(server.PREFIX + "/frame.html")
    assert responses[0] is None
    await response1_promise
    assert responses[1] is not None
    assert responses[2] is None
    await page.goto(server.PREFIX + "/one-style.html")
    await response0_promise
    assert responses[0] is not None
    assert responses[1] is not None
    assert responses[2] is None
    await page.goto(server.PREFIX + "/frame.html?foo=bar")
    await response2_promise
    assert responses[0] is not None
    assert responses[1] is not None
    assert responses[2] is not None
    await page.goto(server.PREFIX + "/empty.html")
    assert responses[0].url == server.PREFIX + "/one-style.html"
    assert responses[1].url == server.PREFIX + "/frame.html"
    assert responses[2].url == server.PREFIX + "/frame.html?foo=bar"


async def test_wait_for_nav_should_work_with_url_match_for_same_document_navigations(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    async with page.expect_navigation(url=re.compile(r"third\.html")) as response_info:
        assert not response_info.is_done()
        await page.evaluate("history.pushState({}, '', '/first.html')")
        assert not response_info.is_done()
        await page.evaluate("history.pushState({}, '', '/second.html')")
        assert not response_info.is_done()
        await page.evaluate("history.pushState({}, '', '/third.html')")
    assert response_info.is_done()


async def test_wait_for_nav_should_work_for_cross_process_navigations(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    url = server.CROSS_PROCESS_PREFIX + "/empty.html"
    async with page.expect_navigation(wait_until="domcontentloaded") as response_info:
        await page.goto(url)
    response = await response_info.value
    assert response.url == url
    assert page.url == url
    assert await page.evaluate("document.location.href") == url


async def test_expect_navigation_should_work_for_cross_process_navigations(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    url = server.CROSS_PROCESS_PREFIX + "/empty.html"
    async with page.expect_navigation(wait_until="domcontentloaded") as response_info:
        goto_task = asyncio.create_task(page.goto(url))
    response = await response_info.value
    assert response.url == url
    assert page.url == url
    assert await page.evaluate("document.location.href") == url
    await goto_task


async def test_wait_for_nav_should_work_with_commit(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE)
    async with page.expect_navigation(wait_until="commit") as response_info:
        await page.evaluate(
            "url => window.location.href = url", server.PREFIX + "/grid.html"
        )
    response = await response_info.value
    assert response.ok
    assert "grid.html" in response.url


async def test_wait_for_load_state_should_respect_timeout(
    page: Page, server: Server
) -> None:
    requests = []

    def handler(request: Any) -> None:
        requests.append(request)

    server.set_route("/one-style.css", handler)

    await page.goto(server.PREFIX + "/one-style.html", wait_until="domcontentloaded")
    with pytest.raises(Error) as exc_info:
        await page.wait_for_load_state("load", timeout=1)
    assert "Timeout 1ms exceeded." in exc_info.value.message


async def test_wait_for_load_state_should_resolve_immediately_if_loaded(
    page: Page, server: Server
) -> None:
    await page.goto(server.PREFIX + "/one-style.html")
    await page.wait_for_load_state()


async def test_wait_for_load_state_should_throw_for_bad_state(
    page: Page, server: Server
) -> None:
    await page.goto(server.PREFIX + "/one-style.html")
    with pytest.raises(Error) as exc_info:
        await page.wait_for_load_state("bad")  # type: ignore
    assert (
        "state: expected one of (load|domcontentloaded|networkidle|commit)"
        in exc_info.value.message
    )


async def test_wait_for_load_state_should_resolve_immediately_if_load_state_matches(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)

    requests = []

    def handler(request: Any) -> None:
        requests.append(request)

    server.set_route("/one-style.css", handler)

    await page.goto(server.PREFIX + "/one-style.html", wait_until="domcontentloaded")
    await page.wait_for_load_state("domcontentloaded")


async def test_wait_for_load_state_networkidle(page: Page, server: Server) -> None:
    wait_for_network_idle_future = asyncio.create_task(
        page.wait_for_load_state("networkidle")
    )
    await page.goto(server.PREFIX + "/networkidle.html")
    await wait_for_network_idle_future


async def test_wait_for_load_state_should_work_with_pages_that_have_loaded_before_being_connected_to(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    async with page.expect_popup() as popup_info:
        await page.evaluate("window._popup = window.open(document.location.href)")

    # The url is about:blank in FF.
    popup = await popup_info.value
    assert popup.url == server.EMPTY_PAGE
    await popup.wait_for_load_state()
    assert popup.url == server.EMPTY_PAGE


async def test_wait_for_load_state_should_wait_for_load_state_of_empty_url_popup(
    page: Page, is_firefox: bool
) -> None:
    ready_state = []
    async with page.expect_popup() as popup_info:
        ready_state.append(
            await page.evaluate(
                """() => {
            popup = window.open('')
            return popup.document.readyState
        }"""
            )
        )

    popup = await popup_info.value
    await popup.wait_for_load_state()
    assert ready_state == ["uninitialized"] if is_firefox else ["complete"]
    assert await popup.evaluate("() => document.readyState") == ready_state[0]


async def test_wait_for_load_state_should_wait_for_load_state_of_about_blank_popup_(
    page: Page,
) -> None:
    async with page.expect_popup() as popup_info:
        await page.evaluate("window.open('about:blank') && 1")
    popup = await popup_info.value
    await popup.wait_for_load_state()
    assert await popup.evaluate("document.readyState") == "complete"


async def test_wait_for_load_state_should_wait_for_load_state_of_about_blank_popup_with_noopener(
    page: Page,
) -> None:
    async with page.expect_popup() as popup_info:
        await page.evaluate("window.open('about:blank', null, 'noopener') && 1")

    popup = await popup_info.value
    await popup.wait_for_load_state()
    assert await popup.evaluate("document.readyState") == "complete"


async def test_wait_for_load_state_should_wait_for_load_state_of_popup_with_network_url_(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    async with page.expect_popup() as popup_info:
        await page.evaluate("url => window.open(url) && 1", server.EMPTY_PAGE)

    popup = await popup_info.value
    await popup.wait_for_load_state()
    assert await popup.evaluate("document.readyState") == "complete"


async def test_wait_for_load_state_should_wait_for_load_state_of_popup_with_network_url_and_noopener_(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    async with page.expect_popup() as popup_info:
        await page.evaluate(
            "url => window.open(url, null, 'noopener') && 1", server.EMPTY_PAGE
        )

    popup = await popup_info.value
    await popup.wait_for_load_state()
    assert await popup.evaluate("document.readyState") == "complete"


async def test_wait_for_load_state_should_work_with_clicking_target__blank(
    page: Page, server: Server
) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.set_content(
        '<a target=_blank rel="opener" href="/one-style.html">yo</a>'
    )
    async with page.expect_popup() as popup_info:
        await page.click("a")
    popup = await popup_info.value
    await popup.wait_for_load_state()
    assert await popup.evaluate("document.readyState") == "complete"


async def test_wait_for_load_state_should_wait_for_load_state_of_new_page(
    context: BrowserContext,
) -> None:
    async with context.expect_page() as page_info:
        await context.new_page()
    new_page = await page_info.value
    await new_page.wait_for_load_state()
    assert await new_page.evaluate("document.readyState") == "complete"


async def test_wait_for_load_state_in_popup(
    context: BrowserContext, server: Server
) -> None:
    page = await context.new_page()
    await page.goto(server.EMPTY_PAGE)
    css_requests = []

    def handle_request(request: TestServerRequest) -> None:
        css_requests.append(request)
        request.write(b"body {}")
        request.finish()

    server.set_route("/one-style.css", handle_request)

    async with page.expect_popup() as popup_info:
        await page.evaluate(
            "url => window.popup = window.open(url)", server.PREFIX + "/one-style.html"
        )

    popup = await popup_info.value
    await popup.wait_for_load_state()
    assert len(css_requests)


async def test_go_back_should_work(page: Page, server: Server) -> None:
    assert await page.go_back() is None

    await page.goto(server.EMPTY_PAGE)
    await page.goto(server.PREFIX + "/grid.html")

    response = await page.go_back()
    assert response
    assert response.ok
    assert server.EMPTY_PAGE in response.url

    response = await page.go_forward()
    assert response
    assert response.ok
    assert "/grid.html" in response.url

    response = await page.go_forward()
    assert response is None


async def test_go_back_should_work_with_history_api(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.evaluate(
        """() => {
        history.pushState({}, '', '/first.html')
        history.pushState({}, '', '/second.html')
    }"""
    )
    assert page.url == server.PREFIX + "/second.html"

    await page.go_back()
    assert page.url == server.PREFIX + "/first.html"
    await page.go_back()
    assert page.url == server.EMPTY_PAGE
    await page.go_forward()
    assert page.url == server.PREFIX + "/first.html"


async def test_frame_goto_should_navigate_subframes(page: Page, server: Server) -> None:
    await page.goto(server.PREFIX + "/frames/one-frame.html")
    assert "/frames/one-frame.html" in page.frames[0].url
    assert "/frames/frame.html" in page.frames[1].url

    response = await page.frames[1].goto(server.EMPTY_PAGE)
    assert response
    assert response.ok
    assert response.frame == page.frames[1]


async def test_frame_goto_should_reject_when_frame_detaches(
    page: Page, server: Server, browser_name: str
) -> None:
    await page.goto(server.PREFIX + "/frames/one-frame.html")

    server.set_route("/one-style.css", lambda _: None)
    wait_for_request_task = asyncio.create_task(
        server.wait_for_request("/one-style.css")
    )
    navigation_task = asyncio.create_task(
        page.frames[1].goto(server.PREFIX + "/one-style.html")
    )
    await wait_for_request_task

    await page.eval_on_selector("iframe", "frame => frame.remove()")
    with pytest.raises(Error) as exc_info:
        await navigation_task
    if browser_name == "chromium":
        assert "net::ERR_FAILED" in exc_info.value.message or (
            "frame was detached" in exc_info.value.message.lower()
        )
    else:
        assert "frame was detached" in exc_info.value.message.lower()


async def test_frame_goto_should_continue_after_client_redirect(
    page: Page, server: Server
) -> None:
    server.set_route("/frames/script.js", lambda _: None)
    url = server.PREFIX + "/frames/child-redirect.html"

    with pytest.raises(Error) as exc_info:
        await page.goto(url, timeout=5000, wait_until="networkidle")

    assert "Timeout 5000ms exceeded." in exc_info.value.message
    assert (
        f'navigating to "{url}", waiting until "networkidle"' in exc_info.value.message
    )


async def test_frame_wait_for_nav_should_work(page: Page, server: Server) -> None:
    await page.goto(server.PREFIX + "/frames/one-frame.html")
    frame = page.frames[1]
    async with frame.expect_navigation() as response_info:
        await frame.evaluate(
            "url => window.location.href = url", server.PREFIX + "/grid.html"
        )
    response = await response_info.value
    assert response.ok
    assert "grid.html" in response.url
    assert response.frame == frame
    assert "/frames/one-frame.html" in page.url


async def test_frame_wait_for_nav_should_fail_when_frame_detaches(
    page: Page, server: Server
) -> None:
    await page.goto(server.PREFIX + "/frames/one-frame.html")
    frame = page.frames[1]
    server.set_route("/empty.html", lambda _: None)
    server.set_route("/one-style.css", lambda _: None)
    with pytest.raises(Error) as exc_info:
        async with frame.expect_navigation():

            async def after_it() -> None:
                await server.wait_for_request("/one-style.html")
                await page.eval_on_selector(
                    "iframe", "frame => setTimeout(() => frame.remove(), 0)"
                )

            await asyncio.gather(
                page.eval_on_selector(
                    "iframe",
                    "frame => frame.contentWindow.location.href = '/one-style.html'",
                ),
                after_it(),
            )
    assert "frame was detached" in exc_info.value.message


async def test_frame_wait_for_load_state_should_work(
    page: Page, server: Server
) -> None:
    await page.goto(server.PREFIX + "/frames/one-frame.html")
    frame = page.frames[1]

    request_future: "asyncio.Future[Route]" = asyncio.Future()
    await page.route(
        server.PREFIX + "/one-style.css",
        lambda route, request: request_future.set_result(route),
    )

    await frame.goto(server.PREFIX + "/one-style.html", wait_until="domcontentloaded")
    request = await request_future
    load_task = asyncio.create_task(frame.wait_for_load_state())
    # give the promise a chance to resolve, even though it shouldn't
    await page.evaluate("1")
    assert not load_task.done()
    asyncio.create_task(request.continue_())
    await load_task


async def test_reload_should_work(page: Page, server: Server) -> None:
    await page.goto(server.EMPTY_PAGE)
    await page.evaluate("window._foo = 10")
    await page.reload()
    assert await page.evaluate("window._foo") is None


async def test_reload_should_work_with_data_url(page: Page, server: Server) -> None:
    await page.goto("data:text/html,hello")
    assert "hello" in await page.content()
    assert await page.reload() is None
    assert "hello" in await page.content()


async def test_should_work_with__blank_target(page: Page, server: Server) -> None:
    def handler(request: TestServerRequest) -> None:
        request.write(
            f'<a href="{server.EMPTY_PAGE}" target="_blank">Click me</a>'.encode()
        )
        request.finish()

    server.set_route("/empty.html", handler)

    await page.goto(server.EMPTY_PAGE)
    await page.click('"Click me"')


async def test_should_work_with_cross_process__blank_target(
    page: Page, server: Server
) -> None:
    def handler(request: TestServerRequest) -> None:
        request.write(
            f'<a href="{server.CROSS_PROCESS_PREFIX}/empty.html" target="_blank">Click me</a>'.encode()
        )
        request.finish()

    server.set_route("/empty.html", handler)

    await page.goto(server.EMPTY_PAGE)
    await page.click('"Click me"')


def expect_ssl_error(error_message: str, browser_name: str) -> None:
    if browser_name == "chromium":
        assert "net::ERR_CERT_AUTHORITY_INVALID" in error_message
    elif browser_name == "webkit":
        if sys.platform == "darwin":
            assert "The certificate for this server is invalid" in error_message
        elif sys.platform == "win32":
            assert "SSL peer certificate or SSH remote key was not OK" in error_message
        else:
            assert "Unacceptable TLS certificate" in error_message
    else:
        assert "SSL_ERROR_UNKNOWN" in error_message
