Skip to content

Commit b5f8151

Browse files
authored
api: use bytes, not string|bytes for file input buffers (#254)
1 parent f565f59 commit b5f8151

10 files changed

+113
-88
lines changed

Diff for: playwright/async_api.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1186,7 +1186,7 @@ async def setInputFiles(
11861186
11871187
Parameters
11881188
----------
1189-
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]}]]
1189+
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
11901190
timeout : Optional[int]
11911191
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.
11921192
noWaitAfter : Optional[bool]
@@ -1662,7 +1662,7 @@ async def setFiles(
16621662
16631663
Parameters
16641664
----------
1665-
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
1665+
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
16661666
timeout : Optional[int]
16671667
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.
16681668
noWaitAfter : Optional[bool]
@@ -2588,7 +2588,7 @@ async def setInputFiles(
25882588
----------
25892589
selector : str
25902590
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.
2591-
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]}]]
2591+
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
25922592
timeout : Optional[int]
25932593
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.
25942594
noWaitAfter : Optional[bool]
@@ -4648,7 +4648,7 @@ async def setInputFiles(
46484648
----------
46494649
selector : str
46504650
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.
4651-
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
4651+
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
46524652
timeout : Optional[int]
46534653
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.
46544654
noWaitAfter : Optional[bool]

Diff for: playwright/element_handle.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
MouseButton,
2828
MousePosition,
2929
SelectOption,
30+
SetFilePayload,
3031
locals_to_params,
3132
)
3233
from playwright.js_handle import (
@@ -291,22 +292,27 @@ def convert_select_option_values(arg: ValuesToSelect) -> Any:
291292

292293
def normalize_file_payloads(
293294
files: Union[str, Path, FilePayload, List[str], List[Path], List[FilePayload]]
294-
) -> List[FilePayload]:
295+
) -> List[SetFilePayload]:
295296
file_list = files if isinstance(files, list) else [files]
296-
file_payloads: List[FilePayload] = []
297+
file_payloads: List[SetFilePayload] = []
297298
for item in file_list:
298299
if isinstance(item, str) or isinstance(item, Path):
299300
with open(item, mode="rb") as fd:
300-
file: FilePayload = {
301-
"name": os.path.basename(item),
302-
"mimeType": mimetypes.guess_type(str(Path(item)))[0]
303-
or "application/octet-stream",
304-
"buffer": base64.b64encode(fd.read()).decode(),
305-
}
306-
file_payloads.append(file)
301+
file_payloads.append(
302+
{
303+
"name": os.path.basename(item),
304+
"mimeType": mimetypes.guess_type(str(Path(item)))[0]
305+
or "application/octet-stream",
306+
"buffer": base64.b64encode(fd.read()).decode(),
307+
}
308+
)
307309
else:
308-
if isinstance(item["buffer"], bytes):
309-
item["buffer"] = base64.b64encode(item["buffer"]).decode()
310-
file_payloads.append(item)
310+
file_payloads.append(
311+
{
312+
"name": item["name"],
313+
"mimeType": item["mimeType"],
314+
"buffer": base64.b64encode(item["buffer"]).decode(),
315+
}
316+
)
311317

312318
return file_payloads

Diff for: playwright/helper.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ class MousePosition(TypedDict):
5959
class FilePayload(TypedDict):
6060
name: str
6161
mimeType: str
62-
buffer: Union[bytes, str]
62+
buffer: bytes
63+
64+
65+
class SetFilePayload(TypedDict):
66+
name: str
67+
mimeType: str
68+
buffer: str
6369

6470

6571
class SelectOption(TypedDict):

Diff for: playwright/sync_api.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,7 @@ def setInputFiles(
12241224
12251225
Parameters
12261226
----------
1227-
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]}]]
1227+
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
12281228
timeout : Optional[int]
12291229
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.
12301230
noWaitAfter : Optional[bool]
@@ -1718,7 +1718,7 @@ def setFiles(
17181718
17191719
Parameters
17201720
----------
1721-
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
1721+
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
17221722
timeout : Optional[int]
17231723
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.
17241724
noWaitAfter : Optional[bool]
@@ -2683,7 +2683,7 @@ def setInputFiles(
26832683
----------
26842684
selector : str
26852685
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.
2686-
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]}]]
2686+
files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": bytes}]]
26872687
timeout : Optional[int]
26882688
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.
26892689
noWaitAfter : Optional[bool]
@@ -4835,7 +4835,7 @@ def setInputFiles(
48354835
----------
48364836
selector : str
48374837
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.
4838-
files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]]
4838+
files : Union[str, {"name": str, "mimeType": str, "buffer": bytes}, List[str], List[{"name": str, "mimeType": str, "buffer": bytes}]]
48394839
timeout : Optional[int]
48404840
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.
48414841
noWaitAfter : Optional[bool]

Diff for: scripts/expected_api_mismatch.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ Parameter not documented: BrowserContext.addInitScript(path=)
7474
Parameter not documented: Page.addInitScript(path=)
7575

7676
# File payload
77-
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]}]]
78-
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]}]]
79-
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]}]]
80-
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]}]]
77+
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}]]
78+
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}]]
79+
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}]]
80+
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}]]
8181

8282
# Select option
8383
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]

Diff for: tests/async/test_click.py

+8-15
Original file line numberDiff line numberDiff line change
@@ -809,13 +809,10 @@ async def test_fail_when_element_detaches_after_animation(page, server):
809809
promise = asyncio.create_task(handle.click())
810810
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
811811
await page.evaluate("stopButton(true)")
812-
error = None
813-
try:
814-
error = await promise
815-
except Error as e:
816-
error = e
812+
with pytest.raises(Error) as exc_info:
813+
await promise
817814
assert await page.evaluate("window.clicked") is None
818-
assert "Element is not attached to the DOM" in error.message
815+
assert "Element is not attached to the DOM" in exc_info.value.message
819816

820817

821818
async def test_retry_when_element_detaches_after_animation(page, server):
@@ -950,16 +947,12 @@ async def test_click_the_button_when_window_inner_width_is_corrupted(page, serve
950947

951948

952949
async def test_timeout_when_click_opens_alert(page, server):
953-
dialog_promise = asyncio.create_task(page.waitForEvent("dialog"))
954-
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
955950
await page.setContent('<div onclick="window.alert(123)">Click me</div>')
956-
error = None
957-
try:
958-
await page.click("div", timeout=3000)
959-
except TimeoutError as e:
960-
error = e
961-
assert "Timeout 3000ms exceeded" in error.message
962-
dialog = await dialog_promise
951+
async with page.expect_event("dialog") as dialog_info:
952+
with pytest.raises(Error) as exc_info:
953+
await page.click("div", timeout=3000)
954+
assert "Timeout 3000ms exceeded" in exc_info.value.message
955+
dialog = await dialog_info.value
963956
await dialog.dismiss()
964957

965958

Diff for: tests/async/test_input.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ async def test_should_emit_event(page: Page, server):
7777

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

8585

8686
async def test_should_work_when_file_input_is_not_attached_to_DOM(page, server):

Diff for: tests/async/test_page.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ async def test_wait_for_request_should_work_with_url_match(page, server):
224224
assert request.url == server.PREFIX + "/digits/1.png"
225225

226226

227-
async def test_wait_for_event_should_fail_with_error_upon_disconnect(page, server):
227+
async def test_wait_for_event_should_fail_with_error_upon_disconnect(page):
228228
future = asyncio.create_task(page.waitForEvent("download"))
229229
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
230230
await page.close()

Diff for: tests/async/test_worker.py

+40-43
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ async def test_workers_page_workers(page, server):
3838

3939

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

7979

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

8988

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

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

122120
async def test_workers_should_clear_upon_cross_process_navigation(server, page):
123121
await page.goto(server.EMPTY_PAGE)
124-
worker_createdpromise = asyncio.create_task(page.waitForEvent("worker"))
125-
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
126-
await page.evaluate(
127-
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
128-
)
129-
worker = await worker_createdpromise
122+
async with page.expect_event("worker") as event_info:
123+
await page.evaluate(
124+
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
125+
)
126+
worker = await event_info.value
130127
assert len(page.workers) == 1
131128
destroyed = []
132129
worker.once("close", lambda _: destroyed.append(True))
@@ -141,14 +138,14 @@ async def test_workers_should_report_network_activity(page, server):
141138
page.goto(server.PREFIX + "/worker/worker.html"),
142139
)
143140
url = server.PREFIX + "/one-style.css"
144-
request_promise = asyncio.create_task(page.waitForRequest(url))
145-
response_promise = asyncio.create_task(page.waitForResponse(url))
146-
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
147-
await worker.evaluate(
148-
"url => fetch(url).then(response => response.text()).then(console.log)", url
149-
)
150-
request = await request_promise
151-
response = await response_promise
141+
async with page.expect_request(url) as request_info, page.expect_response(
142+
url
143+
) as response_info:
144+
await worker.evaluate(
145+
"url => fetch(url).then(response => response.text()).then(console.log)", url
146+
)
147+
request = await request_info.value
148+
response = await response_info.value
152149
assert request.url == url
153150
assert response.request == request
154151
assert response.ok
@@ -158,17 +155,17 @@ async def test_workers_should_report_network_activity_on_worker_creation(page, s
158155
# Chromium needs waitForDebugger enabled for this one.
159156
await page.goto(server.EMPTY_PAGE)
160157
url = server.PREFIX + "/one-style.css"
161-
request_promise = asyncio.create_task(page.waitForRequest(url))
162-
response_promise = asyncio.create_task(page.waitForResponse(url))
163-
await asyncio.sleep(0) # execute scheduled tasks, but don't await them
164-
await page.evaluate(
165-
"""url => new Worker(URL.createObjectURL(new Blob([`
166-
fetch("${url}").then(response => response.text()).then(console.log);
167-
`], {type: 'application/javascript'})))""",
168-
url,
169-
)
170-
request = await request_promise
171-
response = await response_promise
158+
async with page.expect_request(url) as request_info, page.expect_response(
159+
url
160+
) as response_info:
161+
await page.evaluate(
162+
"""url => new Worker(URL.createObjectURL(new Blob([`
163+
fetch("${url}").then(response => response.text()).then(console.log);
164+
`], {type: 'application/javascript'})))""",
165+
url,
166+
)
167+
request = await request_info.value
168+
response = await response_info.value
172169
assert request.url == url
173170
assert response.request == request
174171
assert response.ok

0 commit comments

Comments
 (0)