Skip to content

api: use bytes, not string|bytes for file input buffers #254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions playwright/async_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1186,7 +1186,7 @@ async def setInputFiles(

Parameters
----------
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down Expand Up @@ -1662,7 +1662,7 @@ async def setFiles(

Parameters
----------
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down Expand Up @@ -2588,7 +2588,7 @@ async def setInputFiles(
----------
selector : str
A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details.
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down Expand Up @@ -4648,7 +4648,7 @@ async def setInputFiles(
----------
selector : str
A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details.
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down
30 changes: 18 additions & 12 deletions playwright/element_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
MouseButton,
MousePosition,
SelectOption,
SetFilePayload,
locals_to_params,
)
from playwright.js_handle import (
Expand Down Expand Up @@ -291,22 +292,27 @@ def convert_select_option_values(arg: ValuesToSelect) -> Any:

def normalize_file_payloads(
files: Union[str, Path, FilePayload, List[str], List[Path], List[FilePayload]]
) -> List[FilePayload]:
) -> List[SetFilePayload]:
file_list = files if isinstance(files, list) else [files]
file_payloads: List[FilePayload] = []
file_payloads: List[SetFilePayload] = []
for item in file_list:
if isinstance(item, str) or isinstance(item, Path):
with open(item, mode="rb") as fd:
file: FilePayload = {
"name": os.path.basename(item),
"mimeType": mimetypes.guess_type(str(Path(item)))[0]
or "application/octet-stream",
"buffer": base64.b64encode(fd.read()).decode(),
}
file_payloads.append(file)
file_payloads.append(
{
"name": os.path.basename(item),
"mimeType": mimetypes.guess_type(str(Path(item)))[0]
or "application/octet-stream",
"buffer": base64.b64encode(fd.read()).decode(),
}
)
else:
if isinstance(item["buffer"], bytes):
item["buffer"] = base64.b64encode(item["buffer"]).decode()
file_payloads.append(item)
file_payloads.append(
{
"name": item["name"],
"mimeType": item["mimeType"],
"buffer": base64.b64encode(item["buffer"]).decode(),
}
)

return file_payloads
8 changes: 7 additions & 1 deletion playwright/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,13 @@ class MousePosition(TypedDict):
class FilePayload(TypedDict):
name: str
mimeType: str
buffer: Union[bytes, str]
buffer: bytes


class SetFilePayload(TypedDict):
name: str
mimeType: str
buffer: str


class SelectOption(TypedDict):
Expand Down
8 changes: 4 additions & 4 deletions playwright/sync_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ def setInputFiles(

Parameters
----------
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down Expand Up @@ -1718,7 +1718,7 @@ def setFiles(

Parameters
----------
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down Expand Up @@ -2683,7 +2683,7 @@ def setInputFiles(
----------
selector : str
A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details.
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down Expand Up @@ -4835,7 +4835,7 @@ def setInputFiles(
----------
selector : str
A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details.
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
timeout : Optional[int]
Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods.
noWaitAfter : Optional[bool]
Expand Down
8 changes: 4 additions & 4 deletions scripts/expected_api_mismatch.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ Parameter not documented: BrowserContext.addInitScript(path=)
Parameter not documented: Page.addInitScript(path=)

# File payload
Parameter type mismatch in FileChooser.setFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
Parameter type mismatch in Page.setInputFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
Parameter type mismatch in ElementHandle.setInputFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
Parameter type mismatch in Frame.setInputFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
Parameter type mismatch in FileChooser.setFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
Parameter type mismatch in Page.setInputFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
Parameter type mismatch in ElementHandle.setInputFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
Parameter type mismatch in Frame.setInputFiles(files=): documented as Union[str, List[str], Dict, List[Dict]], code has Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]

# Select option
Parameter type mismatch in ElementHandle.selectOption(values=): documented as Union[str, ElementHandle, List[str], Dict, List[ElementHandle], List[Dict], NoneType], code has Union[str, ElementHandle, {"value": Optional[str], "label": Optional[str], "index": Optional[str]}, List[str], List[ElementHandle], List[{"value": Optional[str], "label": Optional[str], "index": Optional[str]}], NoneType]
Expand Down
23 changes: 8 additions & 15 deletions tests/async/test_click.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,13 +809,10 @@ async def test_fail_when_element_detaches_after_animation(page, server):
promise = asyncio.create_task(handle.click())
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.evaluate("stopButton(true)")
error = None
try:
error = await promise
except Error as e:
error = e
with pytest.raises(Error) as exc_info:
await promise
assert await page.evaluate("window.clicked") is None
assert "Element is not attached to the DOM" in error.message
assert "Element is not attached to the DOM" in exc_info.value.message


async def test_retry_when_element_detaches_after_animation(page, server):
Expand Down Expand Up @@ -950,16 +947,12 @@ async def test_click_the_button_when_window_inner_width_is_corrupted(page, serve


async def test_timeout_when_click_opens_alert(page, server):
dialog_promise = asyncio.create_task(page.waitForEvent("dialog"))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.setContent('<div onclick="window.alert(123)">Click me</div>')
error = None
try:
await page.click("div", timeout=3000)
except TimeoutError as e:
error = e
assert "Timeout 3000ms exceeded" in error.message
dialog = await dialog_promise
async with page.expect_event("dialog") as dialog_info:
with pytest.raises(Error) as exc_info:
await page.click("div", timeout=3000)
assert "Timeout 3000ms exceeded" in exc_info.value.message
dialog = await dialog_info.value
await dialog.dismiss()


Expand Down
8 changes: 4 additions & 4 deletions tests/async/test_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ async def test_should_emit_event(page: Page, server):

async def test_should_work_when_file_input_is_attached_to_DOM(page: Page, server):
await page.setContent("<input type=file>")
file_chooser = asyncio.create_task(page.waitForEvent("filechooser"))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.click("input")
assert await file_chooser
async with page.expect_event("filechooser") as fc_info:
await page.click("input")
file_chooser = await fc_info.value
assert file_chooser


async def test_should_work_when_file_input_is_not_attached_to_DOM(page, server):
Expand Down
2 changes: 1 addition & 1 deletion tests/async/test_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ async def test_wait_for_request_should_work_with_url_match(page, server):
assert request.url == server.PREFIX + "/digits/1.png"


async def test_wait_for_event_should_fail_with_error_upon_disconnect(page, server):
async def test_wait_for_event_should_fail_with_error_upon_disconnect(page):
future = asyncio.create_task(page.waitForEvent("download"))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.close()
Expand Down
83 changes: 40 additions & 43 deletions tests/async/test_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ async def test_workers_page_workers(page, server):


async def test_workers_should_emit_created_and_destroyed_events(page: Page):
worker_createdpromise = asyncio.create_task(page.waitForEvent("worker"))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
worker_obj = await page.evaluateHandle(
"() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'})))"
)
worker = await worker_createdpromise
worker_obj = None
async with page.expect_event("worker") as event_info:
worker_obj = await page.evaluateHandle(
"() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'})))"
)
worker = await event_info.value
worker_this_obj = await worker.evaluateHandle("() => this")
worker_destroyed_promise: Future[Worker] = asyncio.Future()
worker.once("close", lambda w: worker_destroyed_promise.set_result(w))
Expand Down Expand Up @@ -78,12 +78,11 @@ async def test_workers_should_have_JSHandles_for_console_logs(page):


async def test_workers_should_evaluate(page):
worker_createdpromise = asyncio.create_task(page.waitForEvent("worker"))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
)
worker = await worker_createdpromise
async with page.expect_event("worker") as event_info:
await page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
)
worker = await event_info.value
assert await worker.evaluate("1+1") == 2


Expand All @@ -105,12 +104,11 @@ async def test_workers_should_report_errors(page):

async def test_workers_should_clear_upon_navigation(server, page):
await page.goto(server.EMPTY_PAGE)
worker_createdpromise = asyncio.create_task(page.waitForEvent("worker"))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.evaluate(
'() => new Worker(URL.createObjectURL(new Blob(["console.log(1)"], {type: "application/javascript"})))'
)
worker = await worker_createdpromise
async with page.expect_event("worker") as event_info:
await page.evaluate(
'() => new Worker(URL.createObjectURL(new Blob(["console.log(1)"], {type: "application/javascript"})))'
)
worker = await event_info.value
assert len(page.workers) == 1
destroyed = []
worker.once("close", lambda _: destroyed.append(True))
Expand All @@ -121,12 +119,11 @@ async def test_workers_should_clear_upon_navigation(server, page):

async def test_workers_should_clear_upon_cross_process_navigation(server, page):
await page.goto(server.EMPTY_PAGE)
worker_createdpromise = asyncio.create_task(page.waitForEvent("worker"))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
)
worker = await worker_createdpromise
async with page.expect_event("worker") as event_info:
await page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
)
worker = await event_info.value
assert len(page.workers) == 1
destroyed = []
worker.once("close", lambda _: destroyed.append(True))
Expand All @@ -141,14 +138,14 @@ async def test_workers_should_report_network_activity(page, server):
page.goto(server.PREFIX + "/worker/worker.html"),
)
url = server.PREFIX + "/one-style.css"
request_promise = asyncio.create_task(page.waitForRequest(url))
response_promise = asyncio.create_task(page.waitForResponse(url))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await worker.evaluate(
"url => fetch(url).then(response => response.text()).then(console.log)", url
)
request = await request_promise
response = await response_promise
async with page.expect_request(url) as request_info, page.expect_response(
url
) as response_info:
await worker.evaluate(
"url => fetch(url).then(response => response.text()).then(console.log)", url
)
request = await request_info.value
response = await response_info.value
assert request.url == url
assert response.request == request
assert response.ok
Expand All @@ -158,17 +155,17 @@ async def test_workers_should_report_network_activity_on_worker_creation(page, s
# Chromium needs waitForDebugger enabled for this one.
await page.goto(server.EMPTY_PAGE)
url = server.PREFIX + "/one-style.css"
request_promise = asyncio.create_task(page.waitForRequest(url))
response_promise = asyncio.create_task(page.waitForResponse(url))
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
await page.evaluate(
"""url => new Worker(URL.createObjectURL(new Blob([`
fetch("${url}").then(response => response.text()).then(console.log);
`], {type: 'application/javascript'})))""",
url,
)
request = await request_promise
response = await response_promise
async with page.expect_request(url) as request_info, page.expect_response(
url
) as response_info:
await page.evaluate(
"""url => new Worker(URL.createObjectURL(new Blob([`
fetch("${url}").then(response => response.text()).then(console.log);
`], {type: 'application/javascript'})))""",
url,
)
request = await request_info.value
response = await response_info.value
assert request.url == url
assert response.request == request
assert response.ok
Expand Down
Loading