diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5219da8ea..7cfd81568 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: jobs: build: strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: [3.7, 3.8] diff --git a/playwright/__init__.py b/playwright/__init__.py index ea3ae0412..8d5f24fc9 100644 --- a/playwright/__init__.py +++ b/playwright/__init__.py @@ -13,17 +13,20 @@ # limitations under the License. from playwright.main import playwright_object +import playwright.helper as helper chromium = playwright_object.chromium firefox = playwright_object.firefox webkit = playwright_object.webkit devices = playwright_object.devices browser_types = playwright_object.browser_types +TimeoutError = helper.TimeoutError __all__ = [ 'browser_types', 'chromium', 'firefox', 'webkit', - 'devices' + 'devices', + 'TimeoutError' ] diff --git a/playwright/browser_context.py b/playwright/browser_context.py index 9024547cb..d928f5e67 100644 --- a/playwright/browser_context.py +++ b/playwright/browser_context.py @@ -56,14 +56,14 @@ def _on_page(self, page: Page) -> None: def _on_route(self, route: Route, request: Request) -> None: for handler_entry in self._routes: - if handler_entry.matcher.matches(request.url()): + if handler_entry.matcher.matches(request.url): handler_entry.handler(route, request) return asyncio.ensure_future(route.continue_()) def _on_binding(self, binding_call: BindingCall) -> None: func = self._bindings.get(binding_call._initializer['name']) - if func == None: + if func is None: return binding_call.call(func) @@ -84,7 +84,7 @@ async def newPage(self) -> Page: return from_channel(await self._channel.send('newPage')) async def cookies(self, urls: Union[str, List[str]]) -> List[Cookie]: - if urls == None: + if urls is None: urls = list() return await self._channel.send('cookies', dict(urls=urls)) @@ -135,7 +135,7 @@ async def route(self, match: URLMatch, handler: RouteHandler) -> None: await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=True)) async def unroute(self, match: URLMatch, handler: Optional[RouteHandler]) -> None: - self._routes = filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes) + self._routes = list(filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes)) if len(self._routes) == 0: await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=False)) diff --git a/playwright/connection.py b/playwright/connection.py index 48ea81d3f..f523c7868 100644 --- a/playwright/connection.py +++ b/playwright/connection.py @@ -13,7 +13,7 @@ # limitations under the License. import asyncio -from playwright.helper import parse_error +from playwright.helper import parse_error, ParsedMessagePayload from playwright.transport import Transport from pyee import BaseEventEmitter from typing import Any, Awaitable, Dict, List, Optional @@ -22,12 +22,12 @@ class Channel(BaseEventEmitter): def __init__(self, scope: 'ConnectionScope', guid: str) -> None: super().__init__() - self._scope = scope + self._scope: ConnectionScope = scope self._guid = guid - self._object = None + self._object: Optional[ChannelOwner] = None async def send(self, method: str, params: dict = None) -> Any: - if params == None: + if params is None: params = dict() return await self._scope.send_message_to_server(self._guid, method, params) @@ -121,20 +121,22 @@ async def _send_message_to_server(self, guid: str, method: str, params: Dict) -> self._callbacks[id] = callback return await callback - def _dispatch(self, msg: Dict): - guid = msg.get('guid') + def _dispatch(self, msg: ParsedMessagePayload): - if msg.get('id'): - callback = self._callbacks.pop(msg.get('id')) - if msg.get('error'): - callback.set_exception(parse_error(msg.get('error'))) + id = msg.get('id') + if id: + callback = self._callbacks.pop(id) + error = msg.get('error') + if error: + callback.set_exception(parse_error(error)) else: result = self._replace_guids_with_channels(msg.get('result')) callback.set_result(result) return + guid = msg['guid'] method = msg.get('method') - params = msg.get('params') + params = msg['params'] if method == '__create__': scope = self._scopes[guid] scope.create_remote_object(params['type'], params['guid'], params['initializer']) @@ -144,7 +146,7 @@ def _dispatch(self, msg: Dict): object._channel.emit(method, self._replace_guids_with_channels(params)) def _replace_channels_with_guids(self, payload: Any) -> Any: - if payload == None: + if payload is None: return payload if isinstance(payload, list): return list(map(lambda p: self._replace_channels_with_guids(p), payload)) @@ -158,13 +160,13 @@ def _replace_channels_with_guids(self, payload: Any) -> Any: return payload def _replace_guids_with_channels(self, payload: Any) -> Any: - if payload == None: + if payload is None: return payload if isinstance(payload, list): return list(map(lambda p: self._replace_guids_with_channels(p), payload)) if isinstance(payload, dict): if payload.get('guid') in self._objects: - return self._objects[payload.get('guid')]._channel + return self._objects[payload['guid']]._channel result = dict() for key in payload: result[key] = self._replace_guids_with_channels(payload[key]) diff --git a/playwright/console_message.py b/playwright/console_message.py index 4b12bf7a4..7977f8a18 100644 --- a/playwright/console_message.py +++ b/playwright/console_message.py @@ -22,6 +22,9 @@ class ConsoleMessage(ChannelOwner): def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: super().__init__(scope, guid, initializer) + def __str__(self) -> str: + return self.text + @property def type(self) -> str: return self._initializer['type'] diff --git a/playwright/frame.py b/playwright/frame.py index fa95c230e..16c324e9d 100644 --- a/playwright/frame.py +++ b/playwright/frame.py @@ -18,6 +18,7 @@ from playwright.helper import ConsoleMessageLocation, FilePayload, SelectOption, is_function_body, locals_to_params from playwright.js_handle import JSHandle, parse_result, serialize_argument from playwright.network import Request, Response, Route +from playwright.serializers import normalize_file_payloads from typing import Any, Awaitable, Dict, List, Optional, Union, TYPE_CHECKING if TYPE_CHECKING: @@ -203,10 +204,12 @@ async def selectOption(self, async def setInputFiles(self, selector: str, - files: Union[str, FilePayload, List[str], List[FilePayload]], + files:Union[str, FilePayload, List[Union[str, FilePayload]]], timeout: int = None, noWaitAfter: bool = None) -> None: - await self._channel.send('setInputFiles', locals_to_params(locals())) + params = locals_to_params(locals()) + params['files'] = normalize_file_payloads(files) + await self._channel.send('setInputFiles', params) async def type(self, selector: str, diff --git a/playwright/helper.py b/playwright/helper.py index e18b36f4d..793283813 100644 --- a/playwright/helper.py +++ b/playwright/helper.py @@ -17,7 +17,7 @@ import fnmatch import re -from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING, Pattern, cast import sys @@ -37,7 +37,7 @@ class FilePayload(TypedDict): name: str mimeType: str - buffer: bytes + buffer: Union[bytes, str] class FrameMatch(TypedDict): url: URLMatch name: str @@ -49,16 +49,35 @@ class ConsoleMessageLocation(TypedDict): url: Optional[str] lineNumber: Optional[int] columnNumber: Optional[int] -class ErrorPayload(TypedDict): +class ErrorPayload(TypedDict, total=False): message: str name: str stack: str value: Any +class ContinueParameters(TypedDict, total=False): + method: str + headers: Dict[str, str] + postData: str + +class ParsedMessageParams(TypedDict): + type: str + guid: str + initializer: Dict + +class ParsedMessagePayload(TypedDict, total=False): + id: int + guid: str + method: str + params: ParsedMessageParams + result: Any + error: ErrorPayload + + class URLMatcher: def __init__(self, match: URLMatch): - self._callback = None - self._regex_obj = None + self._callback: Optional[Callable[[str], bool]] = None + self._regex_obj: Optional[Pattern] = None if isinstance(match, str): regex = '(?:http://|https://)' + fnmatch.translate(match) self._regex_obj = re.compile(regex) @@ -69,7 +88,9 @@ def __init__(self, match: URLMatch): def matches(self, url: str) -> bool: if self._callback: return self._callback(url) - return self._regex_obj.match(url) + if self._regex_obj: + return cast(bool, self._regex_obj.match(url)) + return False class TimeoutSettings: @@ -79,16 +100,22 @@ def __init__(self, parent: Optional['TimeoutSettings']) -> None: def set_default_timeout(self, timeout): self.timeout = timeout -class Error(BaseException): +class Error(Exception): def __init__(self, message: str, stack: str = None) -> None: self.message = message self.stack = stack -def serialize_error(ex: BaseException) -> ErrorPayload: +class TimeoutError(Error): + pass + +def serialize_error(ex: Exception) -> ErrorPayload: return dict(message=str(ex)) def parse_error(error: ErrorPayload): - return Error(error['message'], error['stack']) + base_error_class = Error + if error.get("name") == "TimeoutError": + base_error_class = TimeoutError + return base_error_class(error['message'], error['stack']) def is_function_body(expression: str) -> bool: expression = expression.strip() diff --git a/playwright/js_handle.py b/playwright/js_handle.py index 918254a65..bd6806a1f 100644 --- a/playwright/js_handle.py +++ b/playwright/js_handle.py @@ -29,6 +29,9 @@ def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None self._preview = self._initializer['preview'] self._channel.on('previewUpdated', lambda preview: self._on_preview_updated(preview)) + def __str__(self) -> str: + return self._preview + def _on_preview_updated(self, preview: str) -> None: self._preview = preview @@ -60,9 +63,6 @@ async def dispose(self) -> None: async def jsonValue(self) -> Any: return parse_result(await self._channel.send('jsonValue')) - def toString(self) -> str: - return self._preview - def is_primitive_value(value: Any): return isinstance(value, bool) or isinstance(value, int) or isinstance(value, float) or isinstance(value, str) @@ -74,7 +74,7 @@ def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any: return dict(h=h) if depth > 100: raise Error('Maximum argument depth exceeded') - if value == None: + if value is None: return dict(v='undefined') if isinstance(value, float): if value == float('inf'): @@ -97,19 +97,19 @@ def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any: return dict(a=result) if isinstance(value, dict): - result = dict() + result: Dict[str, Any] = dict() # type: ignore for name in value: result[name] = serialize_value(value[name], handles, depth + 1) return dict(o=result) return dict(v='undefined') def serialize_argument(arg: Any) -> Any: - guids = list() + guids: List[JSHandle] = list() value = serialize_value(arg, guids, 0) return dict(value=value, guids=guids) def parse_value(value: Any) -> Any: - if value == None: + if value is None: return None if isinstance(value, dict): if 'v' in value: diff --git a/playwright/network.py b/playwright/network.py index 3af1ea97f..e411c8089 100644 --- a/playwright/network.py +++ b/playwright/network.py @@ -15,7 +15,7 @@ import base64 import json from playwright.connection import Channel, ChannelOwner, ConnectionScope, from_nullable_channel, from_channel -from playwright.helper import Error +from playwright.helper import Error, ContinueParameters from typing import Awaitable, Dict, List, Optional, Union, TYPE_CHECKING if TYPE_CHECKING: @@ -99,15 +99,15 @@ async def fulfill(self, status: int = 200, headers: Dict[str,str] = dict(), body await self._channel.send('fulfill', response) async def continue_(self, method: str = None, headers: Dict[str,str] = None, postData: Union[str, bytes] = None) -> None: - overrides = dict() + overrides: ContinueParameters = dict() if method: overrides['method'] = method if headers: overrides['headers'] = headers if isinstance(postData, str): - overrides['postData'] = base64.b64encode(bytes(postData, 'utf-8')) + overrides['postData'] = base64.b64encode(bytes(postData, 'utf-8')).decode() elif isinstance(postData, bytes): - overrides['postData'] = base64.b64encode(postData) + overrides['postData'] = base64.b64encode(postData).decode() await self._channel.send('continue', overrides) diff --git a/playwright/page.py b/playwright/page.py index 743334951..a87bf6115 100644 --- a/playwright/page.py +++ b/playwright/page.py @@ -171,7 +171,7 @@ def frame(self, name: str = None, url: FrameMatch = None) -> Optional[Frame]: for frame in self._frames: if name and frame.name == name: return frame - if url and matcher.matches(frame.url): + if url and matcher and matcher.matches(frame.url): return frame return None @@ -287,7 +287,7 @@ async def waitForRequest(self, urlOrPredicate: Union[str, Callable[[Request], bo matcher = URLMatcher(urlOrPredicate) if isinstance(urlOrPredicate, str) else None def predicate(request: Request) -> bool: if matcher: - return matcher.matches(request.url()) + return matcher.matches(request.url) return urlOrPredicate(request) return self.waitForEvent(Page.Events.Request, predicate=predicate) @@ -295,7 +295,7 @@ async def waitForResponse(self, urlOrPredicate: Union[str, Callable[[Response], matcher = URLMatcher(urlOrPredicate) if isinstance(urlOrPredicate, str) else None def predicate(response: Response) -> bool: if matcher: - return matcher.matches(response.url()) + return matcher.matches(response.url) return urlOrPredicate(response) return self.waitForEvent(Page.Events.Response, predicate=predicate) @@ -354,7 +354,7 @@ async def route(self, match: URLMatch, handler: RouteHandler) -> None: await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=True)) async def unroute(self, match: URLMatch, handler: Optional[RouteHandler]) -> None: - self._routes = filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes) + self._routes = list(filter(lambda r: r.matcher.match != match or (handler and r.handler != handler), self._routes)) if len(self._routes) == 0: await self._channel.send('setNetworkInterceptionEnabled', dict(enabled=False)) @@ -488,7 +488,7 @@ async def uncheck(self, return await self._main_frame.uncheck(**locals_to_params(locals())) async def waitForTimeout(self, timeout: int) -> Awaitable[None]: - return self._main_frame.waitForTimeout(timeout) + return await self._main_frame.waitForTimeout(timeout) async def waitForFunction(self, expression: str, @@ -548,5 +548,5 @@ def call(self, func: FunctionWithSource) -> None: source = dict(context=frame._page.context, page=frame._page, frame=frame) result = func(source, *self._initializer['args']) asyncio.ensure_future(self._channel.send('resolve', dict(result=result))) - except BaseException as e: + except Exception as e: asyncio.ensure_future(self._channel.send('reject', dict(error=serialize_error(e)))) diff --git a/playwright/serializers.py b/playwright/serializers.py new file mode 100644 index 000000000..f770caeb8 --- /dev/null +++ b/playwright/serializers.py @@ -0,0 +1,29 @@ + +from typing import Any, Awaitable, Dict, List, Optional, Union, TYPE_CHECKING +from playwright.helper import ConsoleMessageLocation, FilePayload, SelectOption, is_function_body, locals_to_params +from os import path +import mimetypes +import base64 + +def normalize_file_payloads(files: Union[str, FilePayload, List[Union[str, FilePayload]]]) -> List[FilePayload]: + ff: List[Union[str, FilePayload]] = [] + if (not isinstance(files, list)): + ff = [files] + else: + ff = files + file_payloads: List[FilePayload] = [] + for item in ff: + if isinstance(item, str): + with open(item, mode='rb') as fd: + file: FilePayload = { + "name": path.basename(item), + "mimeType": mimetypes.guess_type(item)[0] or 'application/octet-stream', + "buffer": base64.b64encode(fd.read()).decode() + } + file_payloads.append(file) + else: + if isinstance(item["buffer"], bytes): + item["buffer"] = base64.b64encode(item["buffer"]).decode() + file_payloads.append(item) + + return file_payloads diff --git a/setup.cfg b/setup.cfg index 48d26178c..4b26491e0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,4 @@ [tool:pytest] markers = skip_browser +[mypy] +ignore_missing_imports = True diff --git a/tests/assets/file-to-upload.txt b/tests/assets/file-to-upload.txt new file mode 100644 index 000000000..b4ad11848 --- /dev/null +++ b/tests/assets/file-to-upload.txt @@ -0,0 +1 @@ +contents of the file \ No newline at end of file diff --git a/tests/assets/input/fileupload.html b/tests/assets/input/fileupload.html new file mode 100644 index 000000000..85d2c7ce8 --- /dev/null +++ b/tests/assets/input/fileupload.html @@ -0,0 +1,12 @@ + + + + File upload test + + +
+ + +
+ + \ No newline at end of file diff --git a/tests/test_element_handle.py b/tests/test_element_handle.py index 127601279..558ab8d08 100644 --- a/tests/test_element_handle.py +++ b/tests/test_element_handle.py @@ -33,7 +33,7 @@ async def test_bounding_box_handle_nested_frames(page, server): async def test_bounding_box_return_null_for_invisible_elements(page, server): await page.setContent('
hi
') element = await page.querySelector('div') - assert await element.boundingBox() == None + assert await element.boundingBox() is None async def test_bounding_box_force_a_layout(page, server): await page.setViewportSize({'width':500,'height':500}) @@ -120,14 +120,14 @@ async def test_content_frame_for_non_iframes(page, server, utils): await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE) frame = page.frames[1] element_handle = await frame.evaluateHandle('document.body') - assert await element_handle.contentFrame() == None + assert await element_handle.contentFrame() is None async def test_content_frame_for_document_element(page, server, utils): await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE) frame = page.frames[1] element_handle = await frame.evaluateHandle('document.documentElement') - assert await element_handle.contentFrame() == None + assert await element_handle.contentFrame() is None async def test_owner_frame(page, server, utils): await page.goto(server.EMPTY_PAGE) @@ -434,10 +434,10 @@ async def test_a_nice_preview(page, server): check = await page.querySelector('#check') text = await inner.evaluateHandle('e => e.firstChild') await page.evaluate('1') # Give them a chance to calculate the preview. - assert outer.toString() == 'JSHandle@
' - assert inner.toString() == 'JSHandle@
Text,↵more text
' - assert text.toString() == 'JSHandle@#text=Text,↵more text' - assert check.toString() == 'JSHandle@' + assert str(outer) == 'JSHandle@
' + assert str(inner) == 'JSHandle@
Text,↵more text
' + assert str(text) == 'JSHandle@#text=Text,↵more text' + assert str(check) == 'JSHandle@' async def test_get_attribute(page, server): await page.goto(f'{server.PREFIX}/dom.html') diff --git a/tests/test_frames.py b/tests/test_frames.py index 408c3600a..bb1712b95 100644 --- a/tests/test_frames.py +++ b/tests/test_frames.py @@ -206,7 +206,7 @@ async def test_frame_name(page, server, utils): async def test_frame_parent(page, server, utils): await utils.attach_frame(page, 'frame1', server.EMPTY_PAGE) await utils.attach_frame(page, 'frame2', server.EMPTY_PAGE) - assert page.frames[0].parentFrame == None + assert page.frames[0].parentFrame is None assert page.frames[1].parentFrame == page.mainFrame assert page.frames[2].parentFrame == page.mainFrame diff --git a/tests/test_input.py b/tests/test_input.py new file mode 100644 index 000000000..1b42d516a --- /dev/null +++ b/tests/test_input.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 asyncio +from os import path +import os + +from playwright.page import Page + +FILE_TO_UPLOAD = path.join(os.path.dirname(os.path.realpath(__file__)), 'assets/file-to-upload.txt') + +__dirname = os.path.dirname(os.path.realpath(__file__)) + +async def test_should_upload_the_file(page, server): + await page.goto(server.PREFIX + '/input/fileupload.html') + file_path = path.relpath(FILE_TO_UPLOAD, os.getcwd()) + input = await page.querySelector('input') + await input.setInputFiles(file_path) + assert await page.evaluate('e => e.files[0].name', input) == 'file-to-upload.txt' + assert await page.evaluate('''e => { + reader = new FileReader() + promise = new Promise(fulfill => reader.onload = fulfill) + reader.readAsText(e.files[0]) + return promise.then(() => reader.result) + }''', input) == 'contents of the file' + + +async def test_should_work(page): + await page.setContent('') + await page.setInputFiles('input', path.join(__dirname, 'assets/file-to-upload.txt')) + assert await page.evalOnSelector('input', 'input => input.files.length') == 1 + assert await page.evalOnSelector('input', 'input => input.files[0].name') == 'file-to-upload.txt' + +async def test_should_set_from_memory(page): + await page.setContent('') + await page.setInputFiles('input', files=[{ + "name": 'test.txt', + "mimeType": 'text/plain', + "buffer": b'this is a test'}] + ) + assert await page.evalOnSelector('input', 'input => input.files.length') == 1 + assert await page.evalOnSelector('input', 'input => input.files[0].name') == 'test.txt' + + +async def test_should_emit_event(page: Page, server): + await page.setContent('') + fc_done = asyncio.Future() + page.once('filechooser', lambda file_chooser: fc_done.set_result(file_chooser)), + await page.click('input') + file_chooser = await fc_done + assert file_chooser + +async def test_should_work_when_file_input_is_attached_to_DOM(page:Page, server): + await page.setContent('') + file_chooser = asyncio.ensure_future(page.waitForEvent("filechooser")) + await page.click('input') + assert await file_chooser + +async def test_should_work_when_file_input_is_not_attached_to_DOM(page, server): + await page.evaluate('''() => { + el = document.createElement('input') + el.type = 'file' + el.click() + }''') + assert await page.waitForEvent("filechooser") + +async def test_should_return_the_same_file_chooser_when_there_are_many_watchdogs_simultaneously(page:Page, server): + await page.setContent('') + results = await asyncio.gather( + page.waitForEvent('filechooser'), + page.waitForEvent('filechooser'), + page.evalOnSelector('input', 'input => input.click()'), + ) + assert results[0] == results[1] + +async def test_should_accept_single_file(page:Page, server): + await page.setContent('') + file_chooser = (await asyncio.gather( + page.waitForEvent('filechooser'), + page.click('input'), + ))[0] + assert file_chooser.page == page + assert file_chooser.element + await file_chooser.setFiles(FILE_TO_UPLOAD) + assert await page.evalOnSelector('input', 'input => input.files.length') == 1 + assert await page.evalOnSelector('input', 'input => input.files[0].name') == 'file-to-upload.txt' + + +async def test_should_be_able_to_read_selected_file(page: Page, server): + page.once('filechooser', lambda file_chooser: asyncio.ensure_future(file_chooser.setFiles(FILE_TO_UPLOAD))) + await page.setContent('') + content = await page.evalOnSelector('input', '''async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + const reader = new FileReader(); + const promise = new Promise(fulfill => reader.onload = fulfill); + reader.readAsText(picker.files[0]); + return promise.then(() => reader.result); + }''') + assert content == 'contents of the file' + +async def test_should_be_able_to_reset_selected_files_with_empty_file_list(page:Page, server): + await page.setContent('') + page.once('filechooser', lambda file_chooser: asyncio.ensure_future(file_chooser.setFiles(FILE_TO_UPLOAD))) + file_length_1 = (await asyncio.gather( + page.waitForEvent('filechooser'), + page.evalOnSelector('input', '''async picker => { + picker.click(); + await new Promise(x => picker.oninput = x); + return picker.files.length; + }'''), + ))[1] + assert file_length_1 == 1 + + page.once("filechooser", lambda file_chooser: asyncio.ensure_future(file_chooser.setFiles([]))) + file_length_2 = (await asyncio.gather( + page.waitForEvent('filechooser'), + page.evalOnSelector('input', '''async picker => { + picker.click() + await new Promise(x => picker.oninput = x) + return picker.files.length + }'''), + ))[1] + assert file_length_2 == 0 + +async def test_should_not_accept_multiple_files_for_single_file_input(page, server): + await page.setContent('') + file_chooser = (await asyncio.gather( + page.waitForEvent('filechooser'), + page.click('input'), + ))[0] + error = None + try: + await file_chooser.setFiles([ + path.relative(__dirname + 'assets/file-to-upload.txt'), + path.relative(__dirname + 'assets/pptr.png') + ]) + except Exception as exc: + error = exc + assert error != None + +async def test_should_emit_input_and_change_events(page, server): + events = [] + await page.exposeFunction('eventHandled', lambda e: events.append(e)) + await page.setContent(''' + + ''') + await (await page.querySelector('input')).setInputFiles(FILE_TO_UPLOAD) + assert len(events) == 2 + assert events[0]["type"] == 'input' + assert events[1]["type"] == 'change' + +async def test_should_work_for_single_file_pick(page, server): + await page.setContent('') + file_chooser = (await asyncio.gather( + page.waitForEvent('filechooser'), + page.click('input'), + ))[0] + assert file_chooser.isMultiple == False + +async def test_should_work_for_multiple(page, server): + await page.setContent('') + file_chooser = (await asyncio.gather( + page.waitForEvent('filechooser'), + page.click('input'), + ))[0] + assert file_chooser.isMultiple + +async def test_should_work_for_webkitdirectory(page, server): + await page.setContent('') + file_chooser = (await asyncio.gather( + page.waitForEvent('filechooser'), + page.click('input'), + ))[0] + assert file_chooser.isMultiple + + + diff --git a/tests/test_jshandle.py b/tests/test_jshandle.py index 3138eb3db..fc17fd175 100644 --- a/tests/test_jshandle.py +++ b/tests/test_jshandle.py @@ -167,15 +167,15 @@ async def test_jshandle_as_element_return_none_for_non_elements(page): async def test_jshandle_to_string_work_for_primitives(page): number_handle = await page.evaluateHandle('2') - assert number_handle.toString() == 'JSHandle@2' + assert str(number_handle) == 'JSHandle@2' string_handle = await page.evaluateHandle('"a"') - assert string_handle.toString() == 'JSHandle@a' + assert str(string_handle) == 'JSHandle@a' async def test_jshandle_to_string_work_for_complicated_objects(page): handle = await page.evaluateHandle('window') - assert handle.toString() == 'JSHandle@object' + assert str(handle) == 'JSHandle@object' async def test_jshandle_to_string_work_for_promises(page): handle = await page.evaluateHandle('({b: Promise.resolve(123)})') b_handle = await handle.getProperty('b') - assert b_handle.toString() == 'JSHandle@promise' + assert str(b_handle) == 'JSHandle@promise'