diff --git a/tests/async/test_browsercontext_add_cookies.py b/tests/async/test_browsercontext_add_cookies.py new file mode 100644 index 000000000..19e026cfc --- /dev/null +++ b/tests/async/test_browsercontext_add_cookies.py @@ -0,0 +1,387 @@ +# 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 datetime + +import pytest + +from playwright import Error + + +async def test_should_work(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "password", "value": "123456"}] + ) + assert await page.evaluate("() => document.cookie") == "password=123456" + + +async def test_should_roundtrip_cookie(context, page, server): + await page.goto(server.EMPTY_PAGE) + # @see https://en.wikipedia.org/wiki/Year_2038_problem + date = int(datetime.datetime(2038, 1, 1).timestamp() * 1000) + document_cookie = await page.evaluate( + """timestamp => { + const date = new Date(timestamp); + document.cookie = `username=John Doe;expires=${date.toUTCString()}`; + return document.cookie; + }""", + date, + ) + assert document_cookie == "username=John Doe" + cookies = await context.cookies() + await context.clearCookies() + assert await context.cookies() == [] + await context.addCookies(cookies) + assert await context.cookies() == cookies + + +async def test_should_send_cookie_header(server, context): + cookie = [] + + def handler(request): + cookie.extend(request.requestHeaders.getRawHeaders("cookie")) + request.finish() + + server.set_route("/empty.html", handler) + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "cookie", "value": "value"}] + ) + page = await context.newPage() + await page.goto(server.EMPTY_PAGE) + assert cookie == ["cookie=value"] + + +async def test_should_isolate_cookies_in_browser_contexts(context, server, browser): + another_context = await browser.newContext() + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "isolatecookie", "value": "page1value"}] + ) + await another_context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "isolatecookie", "value": "page2value"}] + ) + + cookies_1 = await context.cookies() + cookies_2 = await another_context.cookies() + assert len(cookies_1) == 1 + assert len(cookies_2) == 1 + assert cookies_1[0]["name"] == "isolatecookie" + assert cookies_1[0]["value"] == "page1value" + assert cookies_2[0]["name"] == "isolatecookie" + assert cookies_2[0]["value"] == "page2value" + await another_context.close() + + +async def test_should_isolate_session_cookies(context, server, browser): + server.set_route( + "/setcookie.html", + lambda r: (r.setHeader("Set-Cookie", "session=value"), r.finish(),), + ) + + page_1 = await context.newPage() + await page_1.goto(server.PREFIX + "/setcookie.html") + ## + page_2 = await context.newPage() + await page_2.goto(server.EMPTY_PAGE) + cookies_2 = await context.cookies() + assert len(cookies_2) == 1 + assert ",".join(list(map(lambda c: c["value"], cookies_2))) == "value" + ## + context_b = await browser.newContext() + page_3 = await context_b.newPage() + await page_3.goto(server.EMPTY_PAGE) + cookies_3 = await context_b.cookies() + assert cookies_3 == [] + await context_b.close() + + +async def test_should_isolate_persistent_cookies(context, server, browser): + server.set_route( + "/setcookie.html", + lambda r: ( + r.setHeader("Set-Cookie", "persistent=persistent-value; max-age=3600"), + r.finish(), + ), + ) + + page = await context.newPage() + await page.goto(server.PREFIX + "/setcookie.html") + + context_1 = context + context_2 = await browser.newContext() + [page_1, page_2] = await asyncio.gather(context_1.newPage(), context_2.newPage()) + await asyncio.gather(page_1.goto(server.EMPTY_PAGE), page_2.goto(server.EMPTY_PAGE)) + [cookies_1, cookies_2] = await asyncio.gather( + context_1.cookies(), context_2.cookies() + ) + assert len(cookies_1) == 1 + assert cookies_1[0]["name"] == "persistent" + assert cookies_1[0]["value"] == "persistent-value" + assert len(cookies_2) == 0 + await context_2.close() + + +async def test_should_isolate_send_cookie_header(server, context, browser): + cookie = [] + + def handler(request): + cookie.extend(request.requestHeaders.getRawHeaders("cookie") or []) + request.finish() + + server.set_route("/empty.html", handler) + + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "sendcookie", "value": "value"}] + ) + + page_1 = await context.newPage() + await page_1.goto(server.EMPTY_PAGE) + assert cookie == ["sendcookie=value"] + cookie.clear() + ## + context_2 = await browser.newContext() + page_2 = await context_2.newPage() + await page_2.goto(server.EMPTY_PAGE) + assert cookie == [] + await context_2.close() + + +async def test_should_isolate_cookies_between_launches(browser_factory, server): + browser_1 = await browser_factory() + context_1 = await browser_1.newContext() + await context_1.addCookies( + [ + { + "url": server.EMPTY_PAGE, + "name": "cookie-in-context-1", + "value": "value", + "expires": int(datetime.datetime.now().timestamp() + 10000), + } + ] + ) + await browser_1.close() + + browser_2 = await browser_factory() + context_2 = await browser_2.newContext() + cookies = await context_2.cookies() + assert cookies == [] + await browser_2.close() + + +async def test_should_set_multiple_cookies(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [ + {"url": server.EMPTY_PAGE, "name": "multiple-1", "value": "123456"}, + {"url": server.EMPTY_PAGE, "name": "multiple-2", "value": "bar"}, + ] + ) + assert ( + await page.evaluate( + """() => { + const cookies = document.cookie.split(';'); + return cookies.map(cookie => cookie.trim()).sort(); + }""" + ) + == ["multiple-1=123456", "multiple-2=bar"] + ) + + +async def test_should_have_expires_set_to_neg_1_for_session_cookies(context, server): + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "expires", "value": "123456"}] + ) + cookies = await context.cookies() + assert cookies[0]["expires"] == -1 + + +async def test_should_set_cookie_with_reasonable_defaults(context, server): + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "defaults", "value": "123456"}] + ) + cookies = await context.cookies() + cookies.sort(key=lambda r: r["name"]) + assert cookies == [ + { + "name": "defaults", + "value": "123456", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + + +async def test_should_set_a_cookie_with_a_path(context, page, server): + await page.goto(server.PREFIX + "/grid.html") + await context.addCookies( + [ + { + "domain": "localhost", + "path": "/grid.html", + "name": "gridcookie", + "value": "GRID", + } + ] + ) + assert await context.cookies() == [ + { + "name": "gridcookie", + "value": "GRID", + "domain": "localhost", + "path": "/grid.html", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + assert await page.evaluate("document.cookie") == "gridcookie=GRID" + await page.goto(server.EMPTY_PAGE) + assert await page.evaluate("document.cookie") == "" + await page.goto(server.PREFIX + "/grid.html") + assert await page.evaluate("document.cookie") == "gridcookie=GRID" + + +async def test_should_not_set_a_cookie_with_blank_page_url(context, server): + with pytest.raises(Error) as exc_info: + await context.addCookies( + [ + {"url": server.EMPTY_PAGE, "name": "example-cookie", "value": "best"}, + {"url": "about:blank", "name": "example-cookie-blank", "value": "best"}, + ] + ) + assert ( + 'Blank page can not have cookie "example-cookie-blank"' + in exc_info.value.message + ) + + +async def test_should_not_set_a_cookie_on_a_data_url_page(context): + with pytest.raises(Error) as exc_info: + await context.addCookies( + [ + { + "url": "data:,Hello%2C%20World!", + "name": "example-cookie", + "value": "best", + } + ] + ) + assert ( + 'Data URL page can not have cookie "example-cookie"' in exc_info.value.message + ) + + +async def test_should_default_to_setting_secure_cookie_for_https_websites( + context, page, server +): + await page.goto(server.EMPTY_PAGE) + SECURE_URL = "https://example.com" + await context.addCookies([{"url": SECURE_URL, "name": "foo", "value": "bar"}]) + [cookie] = await context.cookies(SECURE_URL) + assert cookie["secure"] + + +async def test_should_be_able_to_set_unsecure_cookie_for_http_website( + context, page, server +): + await page.goto(server.EMPTY_PAGE) + HTTP_URL = "http://example.com" + await context.addCookies([{"url": HTTP_URL, "name": "foo", "value": "bar"}]) + [cookie] = await context.cookies(HTTP_URL) + assert not cookie["secure"] + + +async def test_should_set_a_cookie_on_a_different_domain(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": "https://www.example.com", "name": "example-cookie", "value": "best"}] + ) + assert await page.evaluate("document.cookie") == "" + assert await context.cookies("https://www.example.com") == [ + { + "name": "example-cookie", + "value": "best", + "domain": "www.example.com", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": True, + "sameSite": "None", + } + ] + + +async def test_should_set_cookies_for_a_frame(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": server.PREFIX, "name": "frame-cookie", "value": "value"}] + ) + await page.evaluate( + """src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }""", + server.PREFIX + "/grid.html", + ) + + assert await page.frames[1].evaluate("document.cookie") == "frame-cookie=value" + + +async def test_should_not_block_third_party_cookies( + context, page, server, is_chromium, is_firefox +): + await page.goto(server.EMPTY_PAGE) + await page.evaluate( + """src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }""", + server.CROSS_PROCESS_PREFIX + "/grid.html", + ) + await page.frames[1].evaluate("document.cookie = 'username=John Doe'") + await page.waitForTimeout(2000) + allows_third_party = is_chromium or is_firefox + cookies = await context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html") + + if allows_third_party: + assert cookies == [ + { + "domain": "127.0.0.1", + "expires": -1, + "httpOnly": False, + "name": "username", + "path": "/", + "sameSite": "None", + "secure": False, + "value": "John Doe", + } + ] + else: + assert cookies == [] diff --git a/tests/async/test_browsercontext_clearcookies.py b/tests/async/test_browsercontext_clearcookies.py new file mode 100644 index 000000000..014ebd660 --- /dev/null +++ b/tests/async/test_browsercontext_clearcookies.py @@ -0,0 +1,47 @@ +# 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. + + +async def test_should_clear_cookies(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "cookie1", "value": "1"}] + ) + assert await page.evaluate("document.cookie") == "cookie1=1" + await context.clearCookies() + assert await context.cookies() == [] + await page.reload() + assert await page.evaluate("document.cookie") == "" + + +async def test_should_isolate_cookies_when_clearing(context, server, browser): + another_context = await browser.newContext() + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "page1cookie", "value": "page1value"}] + ) + await another_context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "page2cookie", "value": "page2value"}] + ) + + assert len(await context.cookies()) == 1 + assert len(await another_context.cookies()) == 1 + + await context.clearCookies() + assert len(await context.cookies()) == 0 + assert len(await another_context.cookies()) == 1 + + await another_context.clearCookies() + assert len(await context.cookies()) == 0 + assert len(await another_context.cookies()) == 0 + await another_context.close() diff --git a/tests/async/test_browsercontext_cookies.py b/tests/async/test_browsercontext_cookies.py new file mode 100644 index 000000000..4c27e87fc --- /dev/null +++ b/tests/async/test_browsercontext_cookies.py @@ -0,0 +1,192 @@ +# 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 datetime + +import pytest + + +async def test_should_return_no_cookies_in_pristine_browser_context(context): + assert await context.cookies() == [] + + +async def test_should_get_a_cookie(context, page, server): + await page.goto(server.EMPTY_PAGE) + document_cookie = await page.evaluate( + """() => { + document.cookie = 'username=John Doe'; + return document.cookie; + }""" + ) + assert document_cookie == "username=John Doe" + assert await context.cookies() == [ + { + "name": "username", + "value": "John Doe", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + + +async def test_should_get_a_non_session_cookie(context, page, server): + await page.goto(server.EMPTY_PAGE) + # @see https://en.wikipedia.org/wiki/Year_2038_problem + date = int(datetime.datetime(2038, 1, 1).timestamp() * 1000) + document_cookie = await page.evaluate( + """timestamp => { + const date = new Date(timestamp); + document.cookie = `username=John Doe;expires=${date.toUTCString()}`; + return document.cookie; + }""", + date, + ) + assert document_cookie == "username=John Doe" + assert await context.cookies() == [ + { + "name": "username", + "value": "John Doe", + "domain": "localhost", + "path": "/", + "expires": date / 1000, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + + +async def test_should_properly_report_httponly_cookie(context, page, server): + server.set_route( + "/empty.html", + lambda r: ( + r.setHeader("Set-Cookie", "name=value;HttpOnly; Path=/"), + r.finish(), + ), + ) + + await page.goto(server.EMPTY_PAGE) + cookies = await context.cookies() + assert len(cookies) == 1 + assert cookies[0]["httpOnly"] is True + + +async def test_should_properly_report_strict_samesite_cookie( + context, page, server, is_webkit, is_win +): + if is_webkit and is_win: + pytest.skip() + + server.set_route( + "/empty.html", + lambda r: ( + r.setHeader("Set-Cookie", "name=value;SameSite=Strict"), + r.finish(), + ), + ) + await page.goto(server.EMPTY_PAGE) + cookies = await context.cookies() + assert len(cookies) == 1 + assert cookies[0]["sameSite"] == "Strict" + + +async def test_should_properly_report_lax_samesite_cookie( + context, page, server, is_webkit, is_win +): + if is_webkit and is_win: + pytest.skip() + + server.set_route( + "/empty.html", + lambda r: (r.setHeader("Set-Cookie", "name=value;SameSite=Lax"), r.finish(),), + ) + await page.goto(server.EMPTY_PAGE) + cookies = await context.cookies() + assert len(cookies) == 1 + assert cookies[0]["sameSite"] == "Lax" + + +async def test_should_get_multiple_cookies(context, page, server): + await page.goto(server.EMPTY_PAGE) + document_cookie = await page.evaluate( + """() => { + document.cookie = 'username=John Doe'; + document.cookie = 'password=1234'; + return document.cookie.split('; ').sort().join('; '); + }""" + ) + cookies = await context.cookies() + cookies.sort(key=lambda r: r["name"]) + assert document_cookie == "password=1234; username=John Doe" + assert cookies == [ + { + "name": "password", + "value": "1234", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + }, + { + "name": "username", + "value": "John Doe", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + }, + ] + + +async def test_should_get_cookies_from_multiple_urls(context): + await context.addCookies( + [ + {"url": "https://foo.com", "name": "doggo", "value": "woofs"}, + {"url": "https://bar.com", "name": "catto", "value": "purrs"}, + {"url": "https://baz.com", "name": "birdo", "value": "tweets"}, + ] + ) + cookies = await context.cookies(["https://foo.com", "https://baz.com"]) + cookies.sort(key=lambda r: r["name"]) + + assert cookies == [ + { + "name": "birdo", + "value": "tweets", + "domain": "baz.com", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": True, + "sameSite": "None", + }, + { + "name": "doggo", + "value": "woofs", + "domain": "foo.com", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": True, + "sameSite": "None", + }, + ]