playwright-ruby-client 1.37.1 → 1.39.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -7
  3. data/documentation/docs/api/browser.md +13 -9
  4. data/documentation/docs/api/browser_context.md +51 -32
  5. data/documentation/docs/api/browser_type.md +12 -7
  6. data/documentation/docs/api/dialog.md +18 -15
  7. data/documentation/docs/api/download.md +17 -7
  8. data/documentation/docs/api/element_handle.md +7 -19
  9. data/documentation/docs/api/frame.md +55 -30
  10. data/documentation/docs/api/keyboard.md +4 -0
  11. data/documentation/docs/api/locator.md +57 -16
  12. data/documentation/docs/api/page.md +102 -54
  13. data/documentation/docs/api/playwright.md +23 -20
  14. data/documentation/docs/api/request.md +17 -0
  15. data/documentation/docs/api/selectors.md +34 -29
  16. data/documentation/docs/include/api_coverage.md +1 -0
  17. data/lib/playwright/channel.rb +8 -0
  18. data/lib/playwright/channel_owner.rb +7 -2
  19. data/lib/playwright/channel_owners/browser_context.rb +16 -1
  20. data/lib/playwright/channel_owners/local_utils.rb +27 -0
  21. data/lib/playwright/channel_owners/page.rb +2 -0
  22. data/lib/playwright/channel_owners/playwright.rb +1 -24
  23. data/lib/playwright/channel_owners/request.rb +17 -1
  24. data/lib/playwright/channel_owners/route.rb +5 -1
  25. data/lib/playwright/connection.rb +1 -1
  26. data/lib/playwright/console_message_impl.rb +29 -0
  27. data/lib/playwright/errors.rb +13 -2
  28. data/lib/playwright/events.rb +1 -0
  29. data/lib/playwright/javascript/value_parser.rb +8 -0
  30. data/lib/playwright/javascript/value_serializer.rb +10 -4
  31. data/lib/playwright/locator_impl.rb +4 -0
  32. data/lib/playwright/utils.rb +4 -0
  33. data/lib/playwright/version.rb +2 -2
  34. data/lib/playwright_api/browser.rb +2 -2
  35. data/lib/playwright_api/browser_context.rb +4 -4
  36. data/lib/playwright_api/browser_type.rb +2 -2
  37. data/lib/playwright_api/console_message.rb +0 -22
  38. data/lib/playwright_api/dialog.rb +2 -2
  39. data/lib/playwright_api/download.rb +12 -3
  40. data/lib/playwright_api/element_handle.rb +2 -15
  41. data/lib/playwright_api/frame.rb +8 -13
  42. data/lib/playwright_api/keyboard.rb +4 -0
  43. data/lib/playwright_api/locator.rb +52 -16
  44. data/lib/playwright_api/page.rb +24 -29
  45. data/lib/playwright_api/playwright.rb +4 -4
  46. data/lib/playwright_api/request.rb +17 -0
  47. data/lib/playwright_api/selectors.rb +2 -2
  48. data/lib/playwright_api/worker.rb +4 -4
  49. data/sig/playwright.rbs +1 -0
  50. metadata +4 -4
  51. data/lib/playwright/channel_owners/console_message.rb +0 -25
@@ -12,12 +12,21 @@ instance might have multiple [Page](./page) instances.
12
12
 
13
13
  This example creates a page, navigates it to a URL, and then saves a screenshot:
14
14
 
15
- ```ruby
16
- playwright.webkit.launch do |browser|
17
- page = browser.new_page
18
- page.goto('https://example.com/')
19
- page.screenshot(path: 'screenshot.png')
20
- end
15
+ ```python sync title=example_94e620cdbdfd41e2c9b14d561052ffa89535fc346038c4584ea4dd8520f5401c.py
16
+ from playwright.sync_api import sync_playwright, Playwright
17
+
18
+ def run(playwright: Playwright):
19
+ webkit = playwright.webkit
20
+ browser = webkit.launch()
21
+ context = browser.new_context()
22
+ page = context.new_page()
23
+ page.goto("https://example.com")
24
+ page.screenshot(path="screenshot.png")
25
+ browser.close()
26
+
27
+ with sync_playwright() as playwright:
28
+ run(playwright)
29
+
21
30
  ```
22
31
 
23
32
  The Page class emits various events (described below) which can be handled using any of Node's native
@@ -462,18 +471,29 @@ See [BrowserContext#expose_binding](./browser_context#expose_binding) for the co
462
471
 
463
472
  An example of exposing page URL to all frames in a page:
464
473
 
465
- ```ruby
466
- page.expose_binding("pageURL", ->(source) { source[:page].url })
467
- page.content = <<~HTML
468
- <script>
469
- async function onClick() {
470
- document.querySelector('div').textContent = await window.pageURL();
471
- }
472
- </script>
473
- <button onclick="onClick()">Click me</button>
474
- <div></div>
475
- HTML
476
- page.locator("button").click
474
+ ```python sync title=example_4f7d99a72aaea957cc5678ed8728965338d78598d7772f47fbf23c28f0eba52d.py
475
+ from playwright.sync_api import sync_playwright, Playwright
476
+
477
+ def run(playwright: Playwright):
478
+ webkit = playwright.webkit
479
+ browser = webkit.launch(headless=false)
480
+ context = browser.new_context()
481
+ page = context.new_page()
482
+ page.expose_binding("pageURL", lambda source: source["page"].url)
483
+ page.set_content("""
484
+ <script>
485
+ async function onClick() {
486
+ document.querySelector('div').textContent = await window.pageURL();
487
+ }
488
+ </script>
489
+ <button onclick="onClick()">Click me</button>
490
+ <div></div>
491
+ """)
492
+ page.click("button")
493
+
494
+ with sync_playwright() as playwright:
495
+ run(playwright)
496
+
477
497
  ```
478
498
 
479
499
  An example of passing an element handle:
@@ -517,24 +537,35 @@ See [BrowserContext#expose_function](./browser_context#expose_function) for cont
517
537
 
518
538
  An example of adding a `sha256` function to the page:
519
539
 
520
- ```ruby
521
- require 'digest'
540
+ ```python sync title=example_0f68a39bdff02a3df161c74e81cabb8a2ff1f09f0d09f6ef9b799a6f2f19a280.py
541
+ import hashlib
542
+ from playwright.sync_api import sync_playwright, Playwright
522
543
 
523
- def sha1(text)
524
- Digest::SHA256.hexdigest(text)
525
- end
544
+ def sha256(text):
545
+ m = hashlib.sha256()
546
+ m.update(bytes(text, "utf8"))
547
+ return m.hexdigest()
548
+
549
+
550
+ def run(playwright: Playwright):
551
+ webkit = playwright.webkit
552
+ browser = webkit.launch(headless=False)
553
+ page = browser.new_page()
554
+ page.expose_function("sha256", sha256)
555
+ page.set_content("""
556
+ <script>
557
+ async function onClick() {
558
+ document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');
559
+ }
560
+ </script>
561
+ <button onclick="onClick()">Click me</button>
562
+ <div></div>
563
+ """)
564
+ page.click("button")
565
+
566
+ with sync_playwright() as playwright:
567
+ run(playwright)
526
568
 
527
- page.expose_function("sha256", method(:sha256))
528
- page.content = <<~HTML
529
- <script>
530
- async function onClick() {
531
- document.querySelector('div').textContent = await window.sha256('PLAYWRIGHT');
532
- }
533
- </script>
534
- <button onclick="onClick()">Click me</button>
535
- <div></div>
536
- HTML
537
- page.locator("button").click
538
569
  ```
539
570
 
540
571
  ## fill
@@ -554,7 +585,7 @@ This method waits for an element matching `selector`, waits for [actionability](
554
585
 
555
586
  If the target element is not an `<input>`, `<textarea>` or `[contenteditable]` element, this method throws an error. However, if the element is inside the `<label>` element that has an associated [control](https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control), the control will be filled instead.
556
587
 
557
- To send fine-grained keyboard events, use [Page#type](./page#type).
588
+ To send fine-grained keyboard events, use [Locator#press_sequentially](./locator#press_sequentially).
558
589
 
559
590
  ## focus
560
591
 
@@ -1324,13 +1355,14 @@ Triggers a `change` and `input` event once all the provided options have been se
1324
1355
 
1325
1356
  **Usage**
1326
1357
 
1327
- ```ruby
1328
- # single selection matching the value
1329
- page.select_option("select#colors", value: "blue")
1358
+ ```python sync title=example_8260034c740933903e5a39d30a4f4e388bdffa9e82acd9a5fe1fb774752a505a.py
1359
+ # Single selection matching the value or label
1360
+ page.select_option("select#colors", "blue")
1330
1361
  # single selection matching both the label
1331
- page.select_option("select#colors", label: "blue")
1362
+ page.select_option("select#colors", label="blue")
1332
1363
  # multiple selection
1333
- page.select_option("select#colors", value: ["red", "green", "blue"])
1364
+ page.select_option("select#colors", value=["red", "green", "blue"])
1365
+
1334
1366
  ```
1335
1367
 
1336
1368
  ## set_checked
@@ -1519,11 +1551,6 @@ To press a special key, like `Control` or `ArrowDown`, use [Keyboard#press](./ke
1519
1551
 
1520
1552
  **Usage**
1521
1553
 
1522
- ```ruby
1523
- page.type("#mytextarea", "hello") # types instantly
1524
- page.type("#mytextarea", "world", delay: 100) # types slower, like a user
1525
- ```
1526
-
1527
1554
  ## uncheck
1528
1555
 
1529
1556
  ```
@@ -1649,9 +1676,20 @@ Returns when the `expression` returns a truthy value. It resolves to a JSHandle
1649
1676
 
1650
1677
  The [Page#wait_for_function](./page#wait_for_function) can be used to observe viewport size change:
1651
1678
 
1652
- ```ruby
1653
- page.evaluate("window.x = 0; setTimeout(() => { window.x = 100 }, 1000);")
1654
- page.wait_for_function("() => window.x > 0")
1679
+ ```python sync title=example_83eed1f1f00ad73f641bf4a49f672e81c4faf1ca098a4a5070afeeabb88312f5.py
1680
+ from playwright.sync_api import sync_playwright, Playwright
1681
+
1682
+ def run(playwright: Playwright):
1683
+ webkit = playwright.webkit
1684
+ browser = webkit.launch()
1685
+ page = browser.new_page()
1686
+ page.evaluate("window.x = 0; setTimeout(() => { window.x = 100 }, 1000);")
1687
+ page.wait_for_function("() => window.x > 0")
1688
+ browser.close()
1689
+
1690
+ with sync_playwright() as playwright:
1691
+ run(playwright)
1692
+
1655
1693
  ```
1656
1694
 
1657
1695
  To pass an argument to the predicate of [Page#wait_for_function](./page#wait_for_function) function:
@@ -1819,12 +1857,22 @@ function will throw.
1819
1857
 
1820
1858
  This method works across navigations:
1821
1859
 
1822
- ```ruby
1823
- %w[https://google.com https://bbc.com].each do |current_url|
1824
- page.goto(current_url, waitUntil: "domcontentloaded")
1825
- element = page.wait_for_selector("img")
1826
- puts "Loaded image: #{element["src"]}"
1827
- end
1860
+ ```python sync title=example_903c7325fd65fcdf6f22c77fc159922a568841abce60ae1b7c54ab5837401862.py
1861
+ from playwright.sync_api import sync_playwright, Playwright
1862
+
1863
+ def run(playwright: Playwright):
1864
+ chromium = playwright.chromium
1865
+ browser = chromium.launch()
1866
+ page = browser.new_page()
1867
+ for current_url in ["https://google.com", "https://bbc.com"]:
1868
+ page.goto(current_url, wait_until="domcontentloaded")
1869
+ element = page.wait_for_selector("img")
1870
+ print("Loaded image: " + str(element.get_attribute("src")))
1871
+ browser.close()
1872
+
1873
+ with sync_playwright() as playwright:
1874
+ run(playwright)
1875
+
1828
1876
  ```
1829
1877
 
1830
1878
  ## wait_for_timeout
@@ -8,19 +8,20 @@ sidebar_position: 10
8
8
  Playwright module provides a method to launch a browser instance. The following is a typical example of using Playwright
9
9
  to drive automation:
10
10
 
11
- ```ruby
12
- require 'playwright'
11
+ ```python sync title=example_6647e5a44b0440884026a6142606dfddad75ba1e643919b015457df4ed2e198f.py
12
+ from playwright.sync_api import sync_playwright, Playwright
13
13
 
14
- Playwright.create(playwright_cli_executable_path: 'npx playwright') do |playwright|
15
- chromium = playwright.chromium # or "firefox" or "webkit".
16
- chromium.launch do |browser|
17
- page = browser.new_page
18
- page.goto('https://example.com/')
14
+ def run(playwright: Playwright):
15
+ chromium = playwright.chromium # or "firefox" or "webkit".
16
+ browser = chromium.launch()
17
+ page = browser.new_page()
18
+ page.goto("http://example.com")
19
+ # other actions...
20
+ browser.close()
19
21
 
20
- # other actions
22
+ with sync_playwright() as playwright:
23
+ run(playwright)
21
24
 
22
- end
23
- end
24
25
  ```
25
26
 
26
27
  ## chromium
@@ -33,20 +34,22 @@ This object can be used to launch or connect to Chromium, returning instances of
33
34
 
34
35
  Returns a dictionary of devices to be used with [Browser#new_context](./browser#new_context) or [Browser#new_page](./browser#new_page).
35
36
 
36
- ```ruby
37
- require 'playwright'
37
+ ```python sync title=example_14d627977a4ad16a605ec5472d768a3324812fa8e7c57685561408fa6601e352.py
38
+ from playwright.sync_api import sync_playwright, Playwright
38
39
 
39
- Playwright.create(playwright_cli_executable_path: 'npx playwright') do |playwright|
40
- iphone = playwright.devices["iPhone 6"]
41
- playwright.webkit.launch do |browser|
40
+ def run(playwright: Playwright):
41
+ webkit = playwright.webkit
42
+ iphone = playwright.devices["iPhone 6"]
43
+ browser = webkit.launch()
42
44
  context = browser.new_context(**iphone)
43
- page = context.new_page
44
- page.goto('https://example.com/')
45
+ page = context.new_page()
46
+ page.goto("http://example.com")
47
+ # other actions...
48
+ browser.close()
45
49
 
46
- # other actions
50
+ with sync_playwright() as playwright:
51
+ run(playwright)
47
52
 
48
- end
49
- end
50
53
  ```
51
54
 
52
55
  ## firefox
@@ -54,6 +54,20 @@ def frame
54
54
 
55
55
  Returns the [Frame](./frame) that initiated this request.
56
56
 
57
+ **Usage**
58
+
59
+ ```ruby
60
+ frame_url = request.frame.url
61
+ ```
62
+
63
+ **Details**
64
+
65
+ Note that in some cases the frame is not available, and this method will throw.
66
+ - When request originates in the Service Worker. You can use `request.serviceWorker()` to check that.
67
+ - When navigation request is issued before the corresponding frame is created. You can use [Request#navigation_request?](./request#navigation_request?) to check that.
68
+
69
+ Here is an example that handles all the cases:
70
+
57
71
  ## headers
58
72
 
59
73
  ```
@@ -93,6 +107,9 @@ def navigation_request?
93
107
 
94
108
  Whether this request is driving frame's navigation.
95
109
 
110
+ Some navigation requests are issued before the corresponding frame is created, and therefore
111
+ do not have [Request#frame](./request#frame) available.
112
+
96
113
  ## method
97
114
 
98
115
  ```
@@ -21,33 +21,38 @@ Selectors must be registered before creating the page.
21
21
 
22
22
  An example of registering selector engine that queries elements based on a tag name:
23
23
 
24
- ```ruby
25
- tag_selector = <<~JAVASCRIPT
26
- {
27
- // Returns the first element matching given selector in the root's subtree.
28
- query(root, selector) {
29
- return root.querySelector(selector);
30
- },
31
- // Returns all elements matching given selector in the root's subtree.
32
- queryAll(root, selector) {
33
- return Array.from(root.querySelectorAll(selector));
34
- }
35
- }
36
- JAVASCRIPT
37
-
38
- # Register the engine. Selectors will be prefixed with "tag=".
39
- playwright.selectors.register("tag", script: tag_selector)
40
- playwright.chromium.launch do |browser|
41
- page = browser.new_page
42
- page.content = '<div><button>Click me</button></div>'
43
-
44
- # Use the selector prefixed with its name.
45
- button = page.locator('tag=button')
46
- # Combine it with other selector engines.
47
- page.locator('tag=div').get_by_text('Click me').click
48
-
49
- # Can use it in any methods supporting selectors.
50
- button_count = page.locator('tag=button').count
51
- button_count # => 1
52
- end
24
+ ```python sync title=example_3e739d4f0e30e20a6a698e0e17605a841c35e65e75aa3c2642f8bfc368b33f9e.py
25
+ from playwright.sync_api import sync_playwright, Playwright
26
+
27
+ def run(playwright: Playwright):
28
+ tag_selector = """
29
+ {
30
+ // Returns the first element matching given selector in the root's subtree.
31
+ query(root, selector) {
32
+ return root.querySelector(selector);
33
+ },
34
+ // Returns all elements matching given selector in the root's subtree.
35
+ queryAll(root, selector) {
36
+ return Array.from(root.querySelectorAll(selector));
37
+ }
38
+ }"""
39
+
40
+ # Register the engine. Selectors will be prefixed with "tag=".
41
+ playwright.selectors.register("tag", tag_selector)
42
+ browser = playwright.chromium.launch()
43
+ page = browser.new_page()
44
+ page.set_content('<div><button>Click me</button></div>')
45
+
46
+ # Use the selector prefixed with its name.
47
+ button = page.locator('tag=button')
48
+ # Combine it with built-in locators.
49
+ page.locator('tag=div').get_by_text('Click me').click()
50
+ # Can use it in any methods supporting selectors.
51
+ button_count = page.locator('tag=button').count()
52
+ print(button_count)
53
+ browser.close()
54
+
55
+ with sync_playwright() as playwright:
56
+ run(playwright)
57
+
53
58
  ```
@@ -470,6 +470,7 @@
470
470
  * or
471
471
  * page
472
472
  * press
473
+ * press_sequentially
473
474
  * screenshot
474
475
  * scroll_into_view_if_needed
475
476
  * select_option
@@ -30,6 +30,7 @@ module Playwright
30
30
  # @param params [Hash]
31
31
  # @return [Hash]
32
32
  def send_message_to_server_result(method, params)
33
+ check_not_collected
33
34
  with_logging do |metadata|
34
35
  @connection.send_message_to_server(@guid, method, params, metadata: metadata)
35
36
  end
@@ -39,6 +40,7 @@ module Playwright
39
40
  # @param params [Hash]
40
41
  # @returns [Concurrent::Promises::Future]
41
42
  def async_send_message_to_server(method, params = {})
43
+ check_not_collected
42
44
  with_logging do |metadata|
43
45
  @connection.async_send_message_to_server(@guid, method, params, metadata: metadata)
44
46
  end
@@ -74,5 +76,11 @@ module Playwright
74
76
  end,
75
77
  }
76
78
  end
79
+
80
+ private def check_not_collected
81
+ if @object.was_collected?
82
+ raise "The object has been collected to prevent unbounded heap growth."
83
+ end
84
+ end
77
85
  end
78
86
  end
@@ -50,13 +50,18 @@ module Playwright
50
50
  child.send(:update_parent, self)
51
51
  end
52
52
 
53
+ def was_collected?
54
+ @was_collected
55
+ end
56
+
53
57
  # used only from Connection. Not intended for public use. So keep private.
54
- private def dispose!
58
+ private def dispose!(reason: nil)
55
59
  # Clean up from parent and connection.
56
60
  @connection.send(:delete_object_from_channel_owner, @guid)
61
+ @was_collected = reason == 'gc'
57
62
 
58
63
  # Dispose all children.
59
- @objects.each_value { |object| object.send(:dispose!) }
64
+ @objects.each_value { |object| object.send(:dispose!, reason: reason) }
60
65
  @objects.clear
61
66
  end
62
67
 
@@ -36,7 +36,13 @@ module Playwright
36
36
  on_service_worker(ChannelOwners::Worker.from(params['worker']))
37
37
  })
38
38
  @channel.on('console', ->(params) {
39
- on_console_message(ChannelOwners::ConsoleMessage.from(params['message']))
39
+ on_console_message(ConsoleMessageImpl.new(params))
40
+ })
41
+ @channel.on('pageError', ->(params) {
42
+ on_page_error(
43
+ Error.parse(params['error']['error']),
44
+ ChannelOwners::Page.from_nullable(params['page']),
45
+ )
40
46
  })
41
47
  @channel.on('dialog', ->(params) {
42
48
  on_dialog(ChannelOwners::Dialog.from(params['dialog']))
@@ -97,6 +103,8 @@ module Playwright
97
103
  end
98
104
 
99
105
  private def on_route(route)
106
+ route.send(:update_context, self)
107
+
100
108
  # It is not desired to use PlaywrightApi.wrap directly.
101
109
  # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
102
110
  # Just a workaround...
@@ -177,6 +185,13 @@ module Playwright
177
185
  end
178
186
  end
179
187
 
188
+ private def on_page_error(error, page)
189
+ emit(Events::BrowserContext::WebError, WebError.new(error, page))
190
+ if page
191
+ page.emit(Events::Page::PageError, error)
192
+ end
193
+ end
194
+
180
195
  private def on_request(request, page)
181
196
  emit(Events::BrowserContext::Request, request)
182
197
  page&.emit(Events::Page::Request, request)
@@ -1,5 +1,11 @@
1
1
  module Playwright
2
2
  define_channel_owner :LocalUtils do
3
+ def devices
4
+ @devices ||= @initializer['deviceDescriptors'].map do |device|
5
+ [device['name'], parse_device_descriptor(device['descriptor'])]
6
+ end.to_h
7
+ end
8
+
3
9
  # @param zip_file [String]
4
10
  def zip(params)
5
11
  @channel.send_message_to_server('zip', params)
@@ -51,5 +57,26 @@ module Playwright
51
57
  @channel.async_send_message_to_server('addStackToTracingNoReply', callData: { id: id, stack: stack })
52
58
  nil
53
59
  end
60
+
61
+ private def parse_device_descriptor(descriptor)
62
+ # This return value can be passed into Browser#new_context as it is.
63
+ # ex:
64
+ # ```
65
+ # iPhone = playwright.devices['iPhone 6']
66
+ # context = browser.new_context(**iPhone)
67
+ # page = context.new_page
68
+ #
69
+ # ```
70
+ {
71
+ userAgent: descriptor['userAgent'],
72
+ viewport: {
73
+ width: descriptor['viewport']['width'],
74
+ height: descriptor['viewport']['height'],
75
+ },
76
+ deviceScaleFactor: descriptor['deviceScaleFactor'],
77
+ isMobile: descriptor['isMobile'],
78
+ hasTouch: descriptor['hasTouch'],
79
+ }
80
+ end
54
81
  end
55
82
  end
@@ -96,6 +96,8 @@ module Playwright
96
96
  end
97
97
 
98
98
  private def on_route(route)
99
+ route.send(:update_context, self)
100
+
99
101
  # It is not desired to use PlaywrightApi.wrap directly.
100
102
  # However it is a little difficult to define wrapper for `handler` parameter in generate_api.
101
103
  # Just a workaround...
@@ -25,9 +25,7 @@ module Playwright
25
25
  end
26
26
 
27
27
  def devices
28
- @devices ||= @initializer['deviceDescriptors'].map do |item|
29
- [item['name'], parse_device_descriptor(item['descriptor'])]
30
- end.to_h
28
+ @connection.local_utils.devices
31
29
  end
32
30
 
33
31
  # used only from Playwright#connect_to_browser_server
@@ -44,26 +42,5 @@ module Playwright
44
42
  end
45
43
  ::Playwright::ChannelOwners::AndroidDevice.from(@initializer['preConnectedAndroidDevice'])
46
44
  end
47
-
48
- private def parse_device_descriptor(descriptor)
49
- # This return value can be passed into Browser#new_context as it is.
50
- # ex:
51
- # ```
52
- # iPhone = playwright.devices['iPhone 6']
53
- # context = browser.new_context(**iPhone)
54
- # page = context.new_page
55
- #
56
- # ```
57
- {
58
- userAgent: descriptor['userAgent'],
59
- viewport: {
60
- width: descriptor['viewport']['width'],
61
- height: descriptor['viewport']['height'],
62
- },
63
- deviceScaleFactor: descriptor['deviceScaleFactor'],
64
- isMobile: descriptor['isMobile'],
65
- hasTouch: descriptor['hasTouch'],
66
- }
67
- end
68
45
  end
69
46
  end
@@ -86,8 +86,24 @@ module Playwright
86
86
  ChannelOwners::Response.from_nullable(resp)
87
87
  end
88
88
 
89
+ class FramePageNotReadyError < StandardError
90
+ MESSAGE = [
91
+ 'Frame for this navigation request is not available, because the request',
92
+ 'was issued before the frame is created. You can check whether the request',
93
+ 'is a navigation request by calling isNavigationRequest() method.',
94
+ ].join('\n').freeze
95
+
96
+ def initialize
97
+ super(MESSAGE)
98
+ end
99
+ end
100
+
89
101
  def frame
90
- ChannelOwners::Frame.from(@initializer['frame'])
102
+ ChannelOwners::Frame.from(@initializer['frame']).tap do |result|
103
+ unless result.page
104
+ raise FramePageNotReadyError.new
105
+ end
106
+ end
91
107
  end
92
108
 
93
109
  def navigation_request?
@@ -111,7 +111,7 @@ module Playwright
111
111
  end
112
112
 
113
113
  def fetch(headers: nil, method: nil, postData: nil, url: nil, maxRedirects: nil, timeout: nil)
114
- api_request_context = request.frame.page.context.request
114
+ api_request_context = @context.request
115
115
  api_request_context.send(:_inner_fetch,
116
116
  request,
117
117
  url,
@@ -172,5 +172,9 @@ module Playwright
172
172
  mime_types = MIME::Types.type_for(filepath)
173
173
  mime_types.first.to_s || 'application/octet-stream'
174
174
  end
175
+
176
+ private def update_context(context)
177
+ @context = context
178
+ end
175
179
  end
176
180
  end
@@ -181,7 +181,7 @@ module Playwright
181
181
  end
182
182
 
183
183
  if method == "__dispose__"
184
- object.send(:dispose!)
184
+ object.send(:dispose!, reason: params["reason"])
185
185
  return
186
186
  end
187
187
 
@@ -0,0 +1,29 @@
1
+ module Playwright
2
+ define_api_implementation :ConsoleMessageImpl do
3
+ def initialize(event)
4
+ @event = event
5
+ end
6
+
7
+ def page
8
+ @page ||= ChannelOwners::Page.from_nullable(@event['page'])
9
+ end
10
+
11
+ def type
12
+ @event['type']
13
+ end
14
+
15
+ def text
16
+ @event['text']
17
+ end
18
+
19
+ def args
20
+ @event['args']&.map do |arg|
21
+ ChannelOwner.from(arg)
22
+ end
23
+ end
24
+
25
+ def location
26
+ @event['location']
27
+ end
28
+ end
29
+ end