Skip to content

Commit 7a62e1a

Browse files
Fix handler wrapping for keyword-only parameters (#3084)
1 parent 7a387f2 commit 7a62e1a

3 files changed

Lines changed: 117 additions & 1 deletion

File tree

playwright/_impl/_impl_to_api_mapping.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,23 @@ def to_impl(
119119

120120
def wrap_handler(self, handler: Callable[..., Any]) -> Callable[..., None]:
121121
def wrapper_func(*args: Any) -> Any:
122-
arg_count = len(inspect.signature(handler).parameters)
122+
parameters = inspect.signature(handler).parameters
123+
has_varargs = any(
124+
parameter.kind == inspect.Parameter.VAR_POSITIONAL
125+
for parameter in parameters.values()
126+
)
127+
arg_count = (
128+
len(args)
129+
if has_varargs
130+
else sum(
131+
parameter.kind
132+
in (
133+
inspect.Parameter.POSITIONAL_ONLY,
134+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
135+
)
136+
for parameter in parameters.values()
137+
)
138+
)
123139
return handler(
124140
*list(map(lambda a: self.from_maybe_impl(a), args))[:arg_count]
125141
)

tests/async/test_page_add_locator_handler.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,57 @@ async def handler() -> None:
9191
await expect(page.locator("#interstitial")).not_to_be_visible()
9292

9393

94+
async def test_should_work_with_keyword_only_handler(
95+
page: Page, server: Server
96+
) -> None:
97+
await page.goto(server.PREFIX + "/input/handle-locator.html")
98+
99+
called = False
100+
101+
async def handler(*, timeout: object = None) -> None:
102+
nonlocal called
103+
assert timeout is None
104+
called = True
105+
await page.locator("#close").click()
106+
107+
await page.add_locator_handler(
108+
page.get_by_text("This interstitial covers the button"), handler
109+
)
110+
111+
await page.locator("#aside").hover()
112+
await page.evaluate(
113+
'() => { window.clicked = 0; window.setupAnnoyingInterstitial("mouseover", 1); }'
114+
)
115+
await page.locator("#target").click()
116+
assert called
117+
assert await page.evaluate("window.clicked") == 1
118+
await expect(page.locator("#interstitial")).not_to_be_visible()
119+
120+
121+
async def test_should_work_with_varargs_handler(page: Page, server: Server) -> None:
122+
await page.goto(server.PREFIX + "/input/handle-locator.html")
123+
124+
called = False
125+
original_locator = page.get_by_text("This interstitial covers the button")
126+
127+
async def handler(*locators: Locator) -> None:
128+
nonlocal called
129+
assert locators == (original_locator,)
130+
called = True
131+
await page.locator("#close").click()
132+
133+
await page.add_locator_handler(original_locator, handler)
134+
135+
await page.locator("#aside").hover()
136+
await page.evaluate(
137+
'() => { window.clicked = 0; window.setupAnnoyingInterstitial("mouseover", 1); }'
138+
)
139+
await page.locator("#target").click()
140+
assert called
141+
assert await page.evaluate("window.clicked") == 1
142+
await expect(page.locator("#interstitial")).not_to_be_visible()
143+
144+
94145
async def test_should_work_with_locator_hover(page: Page, server: Server) -> None:
95146
await page.goto(server.PREFIX + "/input/handle-locator.html")
96147

tests/sync/test_page_add_locator_handler.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,55 @@ def handler() -> None:
9090
expect(page.locator("#interstitial")).not_to_be_visible()
9191

9292

93+
def test_should_work_with_keyword_only_handler(page: Page, server: Server) -> None:
94+
page.goto(server.PREFIX + "/input/handle-locator.html")
95+
96+
called = False
97+
98+
def handler(*, timeout: object = None) -> None:
99+
nonlocal called
100+
assert timeout is None
101+
called = True
102+
page.locator("#close").click()
103+
104+
page.add_locator_handler(
105+
page.get_by_text("This interstitial covers the button"), handler
106+
)
107+
108+
page.locator("#aside").hover()
109+
page.evaluate(
110+
'() => { window.clicked = 0; window.setupAnnoyingInterstitial("mouseover", 1); }'
111+
)
112+
page.locator("#target").click()
113+
assert called
114+
assert page.evaluate("window.clicked") == 1
115+
expect(page.locator("#interstitial")).not_to_be_visible()
116+
117+
118+
def test_should_work_with_varargs_handler(page: Page, server: Server) -> None:
119+
page.goto(server.PREFIX + "/input/handle-locator.html")
120+
121+
called = False
122+
original_locator = page.get_by_text("This interstitial covers the button")
123+
124+
def handler(*locators: Locator) -> None:
125+
nonlocal called
126+
assert locators == (original_locator,)
127+
called = True
128+
page.locator("#close").click()
129+
130+
page.add_locator_handler(original_locator, handler)
131+
132+
page.locator("#aside").hover()
133+
page.evaluate(
134+
'() => { window.clicked = 0; window.setupAnnoyingInterstitial("mouseover", 1); }'
135+
)
136+
page.locator("#target").click()
137+
assert called
138+
assert page.evaluate("window.clicked") == 1
139+
expect(page.locator("#interstitial")).not_to_be_visible()
140+
141+
93142
def test_should_work_with_locator_hover(page: Page, server: Server) -> None:
94143
page.goto(server.PREFIX + "/input/handle-locator.html")
95144

0 commit comments

Comments
 (0)