playwright-ruby-client 1.14.beta1 → 1.15.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/documentation/docs/api/accessibility.md +16 -17
- data/documentation/docs/api/browser.md +2 -0
- data/documentation/docs/api/browser_type.md +1 -0
- data/documentation/docs/api/element_handle.md +4 -5
- data/documentation/docs/api/experimental/android.md +15 -2
- data/documentation/docs/api/experimental/android_device.md +1 -0
- data/documentation/docs/api/frame.md +62 -106
- data/documentation/docs/api/locator.md +41 -41
- data/documentation/docs/api/mouse.md +3 -4
- data/documentation/docs/api/page.md +6 -6
- data/documentation/docs/api/request.md +15 -19
- data/documentation/docs/api/selectors.md +29 -3
- data/documentation/docs/api/tracing.md +13 -12
- data/documentation/docs/api/worker.md +12 -11
- data/documentation/docs/article/guides/inspector.md +1 -1
- data/documentation/docs/article/guides/playwright_on_alpine_linux.md +1 -1
- data/documentation/docs/article/guides/semi_automation.md +1 -1
- data/documentation/docs/article/guides/use_storage_state.md +78 -0
- data/lib/playwright.rb +44 -4
- data/lib/playwright/channel_owners/browser_type.rb +0 -1
- data/lib/playwright/channel_owners/frame.rb +5 -0
- data/lib/playwright/channel_owners/page.rb +4 -0
- data/lib/playwright/channel_owners/playwright.rb +9 -0
- data/lib/playwright/channel_owners/request.rb +8 -8
- data/lib/playwright/connection.rb +3 -8
- data/lib/playwright/locator_impl.rb +3 -3
- data/lib/playwright/tracing_impl.rb +9 -8
- data/lib/playwright/utils.rb +0 -1
- data/lib/playwright/version.rb +2 -2
- data/lib/playwright_api/android.rb +21 -8
- data/lib/playwright_api/android_device.rb +8 -7
- data/lib/playwright_api/browser.rb +10 -8
- data/lib/playwright_api/browser_context.rb +6 -6
- data/lib/playwright_api/browser_type.rb +8 -7
- data/lib/playwright_api/cdp_session.rb +6 -6
- data/lib/playwright_api/console_message.rb +6 -6
- data/lib/playwright_api/dialog.rb +6 -6
- data/lib/playwright_api/element_handle.rb +7 -7
- data/lib/playwright_api/frame.rb +14 -14
- data/lib/playwright_api/js_handle.rb +6 -6
- data/lib/playwright_api/locator.rb +16 -3
- data/lib/playwright_api/page.rb +13 -13
- data/lib/playwright_api/playwright.rb +6 -6
- data/lib/playwright_api/request.rb +6 -6
- data/lib/playwright_api/response.rb +6 -6
- data/lib/playwright_api/route.rb +6 -6
- data/lib/playwright_api/selectors.rb +38 -7
- data/lib/playwright_api/web_socket.rb +6 -6
- data/lib/playwright_api/worker.rb +6 -6
- metadata +4 -3
@@ -7,10 +7,9 @@ sidebar_position: 10
|
|
7
7
|
Locator represents a view to the element(s) on the page. It captures the logic sufficient to retrieve the element at any
|
8
8
|
given moment. Locator can be created with the [Page#locator](./page#locator) method.
|
9
9
|
|
10
|
-
```
|
10
|
+
```ruby
|
11
11
|
locator = page.locator("text=Submit")
|
12
|
-
locator.click
|
13
|
-
|
12
|
+
locator.click
|
14
13
|
```
|
15
14
|
|
16
15
|
The difference between the Locator and [ElementHandle](./element_handle) is that the latter points to a particular element, while Locator
|
@@ -35,6 +34,19 @@ locator.hover
|
|
35
34
|
locator.click
|
36
35
|
```
|
37
36
|
|
37
|
+
**Strictness**
|
38
|
+
|
39
|
+
Locators are strict. This means that all operations on locators that imply some target DOM element will throw if more
|
40
|
+
than one element matches given selector.
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# Throws if there are several buttons in DOM:
|
44
|
+
page.locator('button').click
|
45
|
+
|
46
|
+
# Works because we explicitly tell locator to pick the first element:
|
47
|
+
page.locator('button').first.click
|
48
|
+
```
|
49
|
+
|
38
50
|
|
39
51
|
|
40
52
|
## all_inner_texts
|
@@ -72,10 +84,12 @@ Elements from child frames return the bounding box relative to the main frame, u
|
|
72
84
|
Assuming the page is static, it is safe to use bounding box coordinates to perform input. For example, the following
|
73
85
|
snippet should click the center of the element.
|
74
86
|
|
75
|
-
```
|
76
|
-
box = element.bounding_box
|
77
|
-
page.mouse.click(
|
78
|
-
|
87
|
+
```ruby
|
88
|
+
box = element.bounding_box
|
89
|
+
page.mouse.click(
|
90
|
+
box["x"] + box["width"] / 2,
|
91
|
+
box["y"] + box["height"] / 2,
|
92
|
+
)
|
79
93
|
```
|
80
94
|
|
81
95
|
|
@@ -177,9 +191,8 @@ The snippet below dispatches the `click` event on the element. Regardless of the
|
|
177
191
|
`click` is dispatched. This is equivalent to calling
|
178
192
|
[element.click()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click).
|
179
193
|
|
180
|
-
```
|
194
|
+
```ruby
|
181
195
|
element.dispatch_event("click")
|
182
|
-
|
183
196
|
```
|
184
197
|
|
185
198
|
Under the hood, it creates an instance of an event based on the given `type`, initializes it with `eventInit` properties
|
@@ -196,11 +209,10 @@ Since `eventInit` is event-specific, please refer to the events documentation fo
|
|
196
209
|
|
197
210
|
You can also specify [JSHandle](./js_handle) as the property value if you want live objects to be passed into the event:
|
198
211
|
|
199
|
-
```
|
212
|
+
```ruby
|
200
213
|
# note you can only create data_transfer in chromium and firefox
|
201
214
|
data_transfer = page.evaluate_handle("new DataTransfer()")
|
202
|
-
element.dispatch_event("
|
203
|
-
|
215
|
+
element.dispatch_event("dragstart", eventInit: { dataTransfer: data_transfer })
|
204
216
|
```
|
205
217
|
|
206
218
|
|
@@ -236,10 +248,9 @@ If `expression` returns a [Promise](https://developer.mozilla.org/en-US/docs/Web
|
|
236
248
|
|
237
249
|
Examples:
|
238
250
|
|
239
|
-
```
|
240
|
-
|
241
|
-
|
242
|
-
|
251
|
+
```ruby
|
252
|
+
tweet = page.query_selector(".tweet .retweets")
|
253
|
+
tweet.evaluate("node => node.innerText") # => "10 retweets"
|
243
254
|
```
|
244
255
|
|
245
256
|
|
@@ -253,15 +264,14 @@ def evaluate_all(expression, arg: nil)
|
|
253
264
|
The method finds all elements matching the specified locator and passes an array of matched elements as a first argument
|
254
265
|
to `expression`. Returns the result of `expression` invocation.
|
255
266
|
|
256
|
-
If `expression` returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), then [
|
257
|
-
value.
|
267
|
+
If `expression` returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), then [Locator#evaluate_all](./locator#evaluate_all) would wait for the promise to resolve and
|
268
|
+
return its value.
|
258
269
|
|
259
270
|
Examples:
|
260
271
|
|
261
|
-
```
|
272
|
+
```ruby
|
262
273
|
elements = page.locator("div")
|
263
|
-
|
264
|
-
|
274
|
+
elements.evaluate_all("(divs, min) => divs.length >= min", arg: 10)
|
265
275
|
```
|
266
276
|
|
267
277
|
|
@@ -368,7 +378,7 @@ Returns the `element.innerText`.
|
|
368
378
|
def input_value(timeout: nil)
|
369
379
|
```
|
370
380
|
|
371
|
-
Returns `input.value` for `<input>` or `<textarea>` element. Throws for non-input elements.
|
381
|
+
Returns `input.value` for `<input>` or `<textarea>` or `<select>` element. Throws for non-input elements.
|
372
382
|
|
373
383
|
## checked?
|
374
384
|
|
@@ -518,26 +528,18 @@ Returns the array of option values that have been successfully selected.
|
|
518
528
|
|
519
529
|
Triggers a `change` and `input` event once all the provided options have been selected.
|
520
530
|
|
521
|
-
```
|
531
|
+
```ruby
|
522
532
|
# single selection matching the value
|
523
|
-
element.select_option("blue")
|
533
|
+
element.select_option(value: "blue")
|
524
534
|
# single selection matching both the label
|
525
|
-
element.select_option(label
|
535
|
+
element.select_option(label: "blue")
|
526
536
|
# multiple selection
|
527
|
-
element.select_option(value
|
528
|
-
|
537
|
+
element.select_option(value: ["red", "green", "blue"])
|
529
538
|
```
|
530
539
|
|
531
|
-
```
|
532
|
-
# single selection matching the value
|
533
|
-
element.select_option("blue")
|
534
|
-
# single selection matching both the value and the label
|
535
|
-
element.select_option(label="blue")
|
536
|
-
# multiple selection
|
537
|
-
element.select_option("red", "green", "blue")
|
540
|
+
```ruby
|
538
541
|
# multiple selection for blue, red and second option
|
539
|
-
element.select_option(value
|
540
|
-
|
542
|
+
element.select_option(value: "blue", index: 2, label: "red")
|
541
543
|
```
|
542
544
|
|
543
545
|
|
@@ -607,19 +609,17 @@ Focuses the element, and then sends a `keydown`, `keypress`/`input`, and `keyup`
|
|
607
609
|
|
608
610
|
To press a special key, like `Control` or `ArrowDown`, use [Locator#press](./locator#press).
|
609
611
|
|
610
|
-
```
|
612
|
+
```ruby
|
611
613
|
element.type("hello") # types instantly
|
612
|
-
element.type("world", delay
|
613
|
-
|
614
|
+
element.type("world", delay: 100) # types slower, like a user
|
614
615
|
```
|
615
616
|
|
616
617
|
An example of typing into a text field and then submitting the form:
|
617
618
|
|
618
|
-
```
|
619
|
+
```ruby
|
619
620
|
element = page.locator("input")
|
620
621
|
element.type("some text")
|
621
622
|
element.press("Enter")
|
622
|
-
|
623
623
|
```
|
624
624
|
|
625
625
|
|
@@ -8,16 +8,15 @@ The Mouse class operates in main-frame CSS pixels relative to the top-left corne
|
|
8
8
|
|
9
9
|
Every `page` object has its own Mouse, accessible with [Page#mouse](./page#mouse).
|
10
10
|
|
11
|
-
```
|
11
|
+
```ruby
|
12
12
|
# using ‘page.mouse’ to trace a 100x100 square.
|
13
13
|
page.mouse.move(0, 0)
|
14
|
-
page.mouse.down
|
14
|
+
page.mouse.down
|
15
15
|
page.mouse.move(0, 100)
|
16
16
|
page.mouse.move(100, 100)
|
17
17
|
page.mouse.move(100, 0)
|
18
18
|
page.mouse.move(0, 0)
|
19
|
-
page.mouse.up
|
20
|
-
|
19
|
+
page.mouse.up
|
21
20
|
```
|
22
21
|
|
23
22
|
|
@@ -269,7 +269,9 @@ def drag_and_drop(
|
|
269
269
|
target,
|
270
270
|
force: nil,
|
271
271
|
noWaitAfter: nil,
|
272
|
+
sourcePosition: nil,
|
272
273
|
strict: nil,
|
274
|
+
targetPosition: nil,
|
273
275
|
timeout: nil,
|
274
276
|
trial: nil)
|
275
277
|
```
|
@@ -620,18 +622,18 @@ def goto(url, referer: nil, timeout: nil, waitUntil: nil)
|
|
620
622
|
Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
|
621
623
|
last redirect.
|
622
624
|
|
623
|
-
|
625
|
+
The method will throw an error if:
|
624
626
|
- there's an SSL error (e.g. in case of self-signed certificates).
|
625
627
|
- target URL is invalid.
|
626
628
|
- the `timeout` is exceeded during navigation.
|
627
629
|
- the remote server does not respond or is unreachable.
|
628
630
|
- the main resource failed to load.
|
629
631
|
|
630
|
-
|
632
|
+
The method will not throw an error when any valid HTTP status code is returned by the remote server, including 404 "Not
|
631
633
|
Found" and 500 "Internal Server Error". The status code for such responses can be retrieved by calling
|
632
634
|
[Response#status](./response#status).
|
633
635
|
|
634
|
-
> NOTE:
|
636
|
+
> NOTE: The method either throws an error or returns a main resource response. The only exceptions are navigation to
|
635
637
|
`about:blank` or navigation to the same URL with a different hash, which would succeed and return `null`.
|
636
638
|
> NOTE: Headless mode doesn't support navigation to a PDF document. See the
|
637
639
|
[upstream issue](https://bugs.chromium.org/p/chromium/issues/detail?id=761295).
|
@@ -686,7 +688,7 @@ Returns `element.innerText`.
|
|
686
688
|
def input_value(selector, strict: nil, timeout: nil)
|
687
689
|
```
|
688
690
|
|
689
|
-
Returns `input.value` for the selected `<input>` or `<textarea>` element. Throws for non-input elements.
|
691
|
+
Returns `input.value` for the selected `<input>` or `<textarea>` or `<select>` element. Throws for non-input elements.
|
690
692
|
|
691
693
|
## checked?
|
692
694
|
|
@@ -756,8 +758,6 @@ The method returns an element locator that can be used to perform actions on the
|
|
756
758
|
element immediately before performing an action, so a series of actions on the same locator can in fact be performed on
|
757
759
|
different DOM elements. That would happen if the DOM structure between those actions has changed.
|
758
760
|
|
759
|
-
Note that locator always implies visibility, so it will always be locating visible elements.
|
760
|
-
|
761
761
|
Shortcut for main frame's [Frame#locator](./frame#locator).
|
762
762
|
|
763
763
|
## main_frame
|
@@ -28,9 +28,8 @@ The method returns `null` unless this request has failed, as reported by `reques
|
|
28
28
|
|
29
29
|
Example of logging of all the failed requests:
|
30
30
|
|
31
|
-
```
|
32
|
-
page.on("requestfailed",
|
33
|
-
|
31
|
+
```ruby
|
32
|
+
page.on("requestfailed", ->(request) { puts "#{request.url} #{request.failure}" })
|
34
33
|
```
|
35
34
|
|
36
35
|
|
@@ -108,18 +107,17 @@ construct the whole redirect chain by repeatedly calling `redirectedFrom()`.
|
|
108
107
|
|
109
108
|
For example, if the website `http://example.com` redirects to `https://example.com`:
|
110
109
|
|
111
|
-
```
|
112
|
-
response = page.goto("http://
|
113
|
-
|
114
|
-
|
110
|
+
```ruby
|
111
|
+
response = page.goto("http://github.com")
|
112
|
+
puts response.url # => "https://github.com"
|
113
|
+
puts response.request.redirected_from&.url # => "http://github.com"
|
115
114
|
```
|
116
115
|
|
117
116
|
If the website `https://google.com` has no redirects:
|
118
117
|
|
119
|
-
```
|
118
|
+
```ruby
|
120
119
|
response = page.goto("https://google.com")
|
121
|
-
|
122
|
-
|
120
|
+
puts response.request.redirected_from&.url # => nil
|
123
121
|
```
|
124
122
|
|
125
123
|
|
@@ -134,9 +132,8 @@ New request issued by the browser if the server responded with redirect.
|
|
134
132
|
|
135
133
|
This method is the opposite of [Request#redirected_from](./request#redirected_from):
|
136
134
|
|
137
|
-
```
|
138
|
-
|
139
|
-
|
135
|
+
```ruby
|
136
|
+
request.redirected_from.redirected_to # equals to request
|
140
137
|
```
|
141
138
|
|
142
139
|
|
@@ -169,12 +166,11 @@ Returns resource timing information for given request. Most of the timing values
|
|
169
166
|
`responseEnd` becomes available when request finishes. Find more information at
|
170
167
|
[Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming).
|
171
168
|
|
172
|
-
```
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
169
|
+
```ruby
|
170
|
+
request = page.expect_event("requestfinished") do
|
171
|
+
page.goto("https://example.com")
|
172
|
+
end
|
173
|
+
puts request.timing
|
178
174
|
```
|
179
175
|
|
180
176
|
|
@@ -15,9 +15,35 @@ def register(name, contentScript: nil, path: nil, script: nil)
|
|
15
15
|
|
16
16
|
An example of registering selector engine that queries elements based on a tag name:
|
17
17
|
|
18
|
-
```
|
19
|
-
|
20
|
-
|
18
|
+
```ruby
|
19
|
+
tag_selector = <<~JAVASCRIPT
|
20
|
+
{
|
21
|
+
// Returns the first element matching given selector in the root's subtree.
|
22
|
+
query(root, selector) {
|
23
|
+
return root.querySelector(selector);
|
24
|
+
},
|
25
|
+
// Returns all elements matching given selector in the root's subtree.
|
26
|
+
queryAll(root, selector) {
|
27
|
+
return Array.from(root.querySelectorAll(selector));
|
28
|
+
}
|
29
|
+
}
|
30
|
+
JAVASCRIPT
|
31
|
+
|
32
|
+
# Register the engine. Selectors will be prefixed with "tag=".
|
33
|
+
playwright.selectors.register("tag", script: tag_selector)
|
34
|
+
playwright.chromium.launch do |browser|
|
35
|
+
page = browser.new_page()
|
36
|
+
page.content = '<div><button>Click me</button></div>'
|
37
|
+
|
38
|
+
# Use the selector prefixed with its name.
|
39
|
+
button = page.query_selector('tag=button')
|
40
|
+
# Combine it with other selector engines.
|
41
|
+
page.click('tag=div >> text="Click me"')
|
42
|
+
|
43
|
+
# Can use it in any methods supporting selectors.
|
44
|
+
button_count = page.eval_on_selector_all('tag=button', 'buttons => buttons.length')
|
45
|
+
button_count # => 1
|
46
|
+
end
|
21
47
|
```
|
22
48
|
|
23
49
|
|
@@ -9,13 +9,14 @@ Playwright script runs.
|
|
9
9
|
|
10
10
|
Start with specifying the folder traces will be stored in:
|
11
11
|
|
12
|
-
```
|
13
|
-
browser
|
14
|
-
context =
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
```ruby
|
13
|
+
browser.new_page do |page|
|
14
|
+
context = page.context
|
15
|
+
|
16
|
+
context.tracing.start(screenshots: true, snapshots: true)
|
17
|
+
page.goto('https://playwright.dev')
|
18
|
+
context.tracing.stop(path: 'trace.zip')
|
19
|
+
end
|
19
20
|
```
|
20
21
|
|
21
22
|
|
@@ -28,12 +29,12 @@ def start(name: nil, screenshots: nil, snapshots: nil)
|
|
28
29
|
|
29
30
|
Start tracing.
|
30
31
|
|
31
|
-
```
|
32
|
-
context
|
33
|
-
page.goto("https://playwright.dev")
|
34
|
-
context.tracing.stop()
|
35
|
-
context.tracing.stop(path = "trace.zip")
|
32
|
+
```ruby
|
33
|
+
context = page.context
|
36
34
|
|
35
|
+
context.tracing.start(name: 'trace', screenshots: true, snapshots: true)
|
36
|
+
page.goto('https://playwright.dev')
|
37
|
+
context.tracing.stop(path: 'trace.zip')
|
37
38
|
```
|
38
39
|
|
39
40
|
|
@@ -8,17 +8,18 @@ The Worker class represents a [WebWorker](https://developer.mozilla.org/en-US/do
|
|
8
8
|
event is emitted on the page object to signal a worker creation. `close` event is emitted on the worker object when the
|
9
9
|
worker is gone.
|
10
10
|
|
11
|
-
```
|
12
|
-
def handle_worker(worker)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
```ruby
|
12
|
+
def handle_worker(worker)
|
13
|
+
puts "worker created: #{worker.url}"
|
14
|
+
worker.once("close", -> (w) { puts "worker destroyed: #{w.url}" })
|
15
|
+
end
|
16
|
+
|
17
|
+
page.on('worker', method(:handle_worker))
|
18
|
+
|
19
|
+
puts "current workers:"
|
20
|
+
page.workers.each do |worker|
|
21
|
+
puts " #{worker.url}"
|
22
|
+
end
|
22
23
|
```
|
23
24
|
|
24
25
|
|
@@ -10,7 +10,7 @@ This allow us to intermediate into automation, for example
|
|
10
10
|
* Authenticate with OAuth2 manually before automation
|
11
11
|
* Testing a page after some chrome extensions are installed manually
|
12
12
|
|
13
|
-
Keep in mind repeatedly that persistent browser context is NOT RECOMMENDED for most cases because it would bring many side effects.
|
13
|
+
Keep in mind repeatedly that persistent browser context is NOT RECOMMENDED for most cases because it would bring many side effects. Consider [reusing cookie and local storage](./use_storage_state) when you just want to keep authenticated across browser contexts.
|
14
14
|
|
15
15
|
## Pause automation for manual operation
|
16
16
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
---
|
2
|
+
sidebar_position: 6
|
3
|
+
---
|
4
|
+
|
5
|
+
# Reuse Cookie and LocalStorage
|
6
|
+
|
7
|
+
In most cases, authentication state is stored in cookie or local storage. When we just want to keep authenticated, it is a good solution to dump/load 'storage state' (= Cookie + LocalStorage).
|
8
|
+
https://playwright.dev/docs/next/auth#reuse-authentication-state
|
9
|
+
|
10
|
+
* Dump storage state using [BrowserContext#storage_state](/docs/api/browser_context#storage_state) with `path: /path/to/state.json`
|
11
|
+
* Load storage state by specifying the parameter `storageState: /path/to/state.json` into [Browser#new_context](/docs/api/browser#new_context) or [Browser#new_page](/docs/api/browser#new_page)
|
12
|
+
|
13
|
+
## Example
|
14
|
+
|
15
|
+
Generally in browser automation, it is very difficult to bypass 2FA or reCAPTCHA in login screen. In such cases, we would consider
|
16
|
+
|
17
|
+
* Authenticate manually by hand
|
18
|
+
* Resume automation with the authentication result
|
19
|
+
|
20
|
+
|
21
|
+
```ruby {16,21}
|
22
|
+
require 'playwright'
|
23
|
+
require 'pry'
|
24
|
+
|
25
|
+
force_login = !File.exist?('github_state.json')
|
26
|
+
|
27
|
+
Playwright.create(playwright_cli_executable_path: 'npx playwright') do |playwright|
|
28
|
+
if force_login
|
29
|
+
# Use headful mode for manual operation.
|
30
|
+
playwright.chromium.launch(headless: false, channel: 'chrome') do |browser|
|
31
|
+
page = browser.new_page
|
32
|
+
page.goto('https://github.com/login')
|
33
|
+
|
34
|
+
# Login manually.
|
35
|
+
binding.pry
|
36
|
+
|
37
|
+
page.context.storage_state(path: 'github_state.json')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
playwright.chromium.launch do |browser|
|
42
|
+
page = browser.new_page(storageState: 'github_state.json')
|
43
|
+
page.goto('https://github.com/notifications')
|
44
|
+
page.screenshot(path: 'github_notification.png')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
When we execute this script at the first time (without github_state.json), login screen is shown:
|
50
|
+
|
51
|
+

|
52
|
+
|
53
|
+
and input credentials manually:
|
54
|
+
|
55
|
+

|
56
|
+
|
57
|
+
and hit `exit` in Pry console.
|
58
|
+
|
59
|
+
```
|
60
|
+
|
61
|
+
9:
|
62
|
+
10: # Login manually. Hit `exit` in Pry console after authenticated.
|
63
|
+
11: require 'pry'
|
64
|
+
12: binding.pry
|
65
|
+
13:
|
66
|
+
=> 14: page.context.storage_state(path: 'github_state.json')
|
67
|
+
15: end if force_login
|
68
|
+
16:
|
69
|
+
17: playwright.chromium.launch do |browser|
|
70
|
+
18: page = browser.new_page(storageState: 'github_state.json')
|
71
|
+
19: page.goto('https://github.com/notifications')
|
72
|
+
|
73
|
+
[1] pry(main)> exit
|
74
|
+
```
|
75
|
+
|
76
|
+
then we can enjoy automation with keeping authenticated. Login screen is never shown until github_state.json is deleted :)
|
77
|
+
|
78
|
+

|