playwright-rs 0.13.0

Rust bindings for Microsoft Playwright
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
use crate::test_server::TestServer;
use playwright_rs::api::IgnoreDefaultArgs;
use playwright_rs::protocol::{BrowserContextOptions, Playwright, Viewport};
use tempfile::TempDir;

#[tokio::test]
async fn test_launch_persistent_context_basic() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_basic: Starting");

    let server = TestServer::start().await;

    // Create temporary directory for user data
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

    // Launch Playwright
    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    let chromium = playwright.chromium();

    // Launch persistent context with basic options
    tracing::debug!("[TEST] Launching persistent context...");
    let context = chromium
        .launch_persistent_context(&user_data_dir)
        .await
        .expect("Failed to launch persistent context");

    // Verify context was created
    tracing::debug!("[TEST] Context created successfully");

    // Create a page and verify it works
    let page = context.new_page().await.expect("Failed to create page");
    page.goto(&server.url(), None)
        .await
        .expect("Failed to navigate");

    // Cleanup
    context.close().await.expect("Failed to close context");
    server.shutdown();
    tracing::debug!("[TEST] test_launch_persistent_context_basic: Complete");
}

#[tokio::test]
async fn test_launch_persistent_context_with_options() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_with_options: Starting");

    let server = TestServer::start().await;

    // Create temporary directory for user data
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    let chromium = playwright.chromium();

    // Create options with viewport and headless
    let options = BrowserContextOptions::builder()
        .viewport(Viewport {
            width: 1280,
            height: 720,
        })
        .build();

    // Launch persistent context with options
    let context = chromium
        .launch_persistent_context_with_options(&user_data_dir, options)
        .await
        .expect("Failed to launch persistent context with options");

    // Verify context works with options
    let page = context.new_page().await.expect("Failed to create page");
    page.goto(&server.url(), None)
        .await
        .expect("Failed to navigate");

    // Cleanup
    context.close().await.expect("Failed to close context");
    server.shutdown();
    tracing::debug!("[TEST] test_launch_persistent_context_with_options: Complete");
}

#[tokio::test]
async fn test_launch_persistent_context_app_mode() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_app_mode: Starting");

    let server = TestServer::start().await;

    // Create temporary directory for user data
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    let chromium = playwright.chromium();

    // Launch with app mode args
    let options = BrowserContextOptions::builder()
        .args(vec![format!("--app={}", server.url())])
        .headless(true) // App mode works in headless
        .build();

    let context = chromium
        .launch_persistent_context_with_options(&user_data_dir, options)
        .await
        .expect("Failed to launch persistent context in app mode");

    // Verify context was created
    // Note: In app mode, browser opens directly to the URL
    let _page = context.new_page().await.expect("Failed to create page");

    // Cleanup
    context.close().await.expect("Failed to close context");
    server.shutdown();
    tracing::debug!("[TEST] test_launch_persistent_context_app_mode: Complete");
}

#[tokio::test]
async fn test_launch_persistent_context_storage_persistence() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_storage_persistence: Starting");

    // Use a single test server — both sessions navigate to the same origin
    // so localStorage persists across context restarts
    let server = TestServer::start().await;
    let url = server.url();

    // Create temporary directory for user data
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    let chromium = playwright.chromium();

    // First session: set local storage
    {
        let context = chromium
            .launch_persistent_context(&user_data_dir)
            .await
            .expect("Failed to launch first persistent context");

        let page = context.new_page().await.expect("Failed to create page");
        page.goto(&url, None).await.expect("Failed to navigate");

        // Set local storage value
        page.evaluate_expression("localStorage.setItem('test_key', 'test_value')")
            .await
            .expect("Failed to set local storage");

        context.close().await.expect("Failed to close context");
    }

    // Second session: verify local storage persisted
    {
        let context = chromium
            .launch_persistent_context(&user_data_dir)
            .await
            .expect("Failed to launch second persistent context");

        let page = context.new_page().await.expect("Failed to create page");
        page.goto(&url, None).await.expect("Failed to navigate");

        // Retrieve local storage value
        let stored_value = page
            .evaluate_value("localStorage.getItem('test_key')")
            .await
            .expect("Failed to get local storage");

        assert_eq!(stored_value, "test_value", "Storage did not persist");

        context.close().await.expect("Failed to close context");
    }

    server.shutdown();
    tracing::debug!("[TEST] test_launch_persistent_context_storage_persistence: Complete");
}

#[tokio::test]
async fn test_launch_persistent_context_error_handling() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_error_handling: Starting");

    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    let chromium = playwright.chromium();

    // Test with invalid user data directory (non-existent parent)
    let invalid_dir = "/nonexistent/path/to/userdata";

    let result = chromium.launch_persistent_context(invalid_dir).await;

    // Should return an error (though Playwright might create the directory)
    // This test mainly verifies the API accepts the parameter
    match result {
        Ok(context) => {
            // If it succeeds (Playwright created the directory), clean up
            let _ = context.close().await;
        }
        Err(_) => {
            // Error is acceptable
        }
    }

    tracing::debug!("[TEST] test_launch_persistent_context_error_handling: Complete");
}

#[tokio::test]
#[ignore]
async fn test_launch_persistent_context_cross_browser() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_cross_browser: Starting");

    let server = TestServer::start().await;
    let url = server.url();

    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    // Test Chromium
    {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

        let chromium = playwright.chromium();
        let context = chromium
            .launch_persistent_context(&user_data_dir)
            .await
            .expect("Failed to launch Chromium persistent context");

        let page = context.new_page().await.expect("Failed to create page");
        page.goto(&url, None)
            .await
            .expect("Failed to navigate in Chromium");

        context.close().await.expect("Failed to close Chromium");
        tracing::info!("✓ Chromium persistent context works");
    }

    // Test Firefox
    {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

        let firefox = playwright.firefox();
        let context = firefox
            .launch_persistent_context(&user_data_dir)
            .await
            .expect("Failed to launch Firefox persistent context");

        let page = context.new_page().await.expect("Failed to create page");
        page.goto(&url, None)
            .await
            .expect("Failed to navigate in Firefox");

        context.close().await.expect("Failed to close Firefox");
        tracing::info!("✓ Firefox persistent context works");
    }

    // Test WebKit (works on macOS/Linux since Playwright 1.58.2).
    //
    // Known Windows limitation (issue #39): WebKit persistent context fails on
    // native Windows with "Initial load failed" in
    // wkPage.js:handleProvisionalLoadFailed. Microsoft tracks this under
    // playwright#36936 (WebKit crashes on Win10/Server 16) and is building a
    // `channel: "webkit-wsl"` replacement (playwright#37036). Re-test after
    // webkit-wsl GAs or WebKit Windows builds stabilize.
    if !cfg!(target_os = "windows") {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

        let webkit = playwright.webkit();
        let context = webkit
            .launch_persistent_context(&user_data_dir)
            .await
            .expect("Failed to launch WebKit persistent context");

        let page = context.new_page().await.expect("Failed to create page");
        page.goto(&url, None)
            .await
            .expect("Failed to navigate in WebKit");

        context.close().await.expect("Failed to close WebKit");
        tracing::info!("✓ WebKit persistent context works");
    } else {
        tracing::warn!("Skipping WebKit persistent context test on Windows (issue #39)");
    }

    server.shutdown();
    tracing::debug!("[TEST] test_launch_persistent_context_cross_browser: Complete");
}

#[tokio::test]
async fn test_launch_persistent_context_ignore_default_args_bool() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_ignore_default_args_bool: Starting");

    let server = TestServer::start().await;

    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    let chromium = playwright.chromium();

    // Launch with ignore_default_args(false) — uses default args (no-op but tests the path)
    // Note: Bool(true) removes ALL default args which can cause browser launch failures
    // in CI environments; the protocol normalization is tested via unit tests.
    let options = BrowserContextOptions::builder()
        .ignore_default_args(IgnoreDefaultArgs::Bool(false))
        .build();

    let context = chromium
        .launch_persistent_context_with_options(&user_data_dir, options)
        .await
        .expect("Failed to launch persistent context with ignore_default_args(false)");

    let page = context.new_page().await.expect("Failed to create page");
    page.goto(&server.url(), None)
        .await
        .expect("Failed to navigate");

    context.close().await.expect("Failed to close context");
    server.shutdown();
    tracing::debug!("[TEST] test_launch_persistent_context_ignore_default_args_bool: Complete");
}

#[tokio::test]
async fn test_launch_persistent_context_ignore_default_args_array() {
    crate::common::init_tracing();
    tracing::debug!("[TEST] test_launch_persistent_context_ignore_default_args_array: Starting");

    let server = TestServer::start().await;

    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let user_data_dir = temp_dir.path().to_str().unwrap().to_string();

    let playwright = Playwright::launch()
        .await
        .expect("Failed to launch Playwright");

    let chromium = playwright.chromium();

    // Launch with ignore_default_args filtering specific args
    let options = BrowserContextOptions::builder()
        .ignore_default_args(IgnoreDefaultArgs::Array(vec![
            "--disable-popup-blocking".to_string(),
        ]))
        .build();

    let context = chromium
        .launch_persistent_context_with_options(&user_data_dir, options)
        .await
        .expect("Failed to launch persistent context with ignore_default_args(array)");

    let page = context.new_page().await.expect("Failed to create page");
    page.goto(&server.url(), None)
        .await
        .expect("Failed to navigate");

    context.close().await.expect("Failed to close context");
    server.shutdown();
    tracing::debug!("[TEST] test_launch_persistent_context_ignore_default_args_array: Complete");
}

#[tokio::test]
async fn test_launch_with_artifacts_dir() {
    crate::common::init_tracing();

    use playwright_rs::api::LaunchOptions;

    let pw = Playwright::launch()
        .await
        .expect("Failed to launch playwright");

    // Create a fresh artifacts dir for this run; verify Playwright accepts
    // the option and the launch succeeds. We can't assert exact artifact
    // file placement (Playwright may write into subdirectories) — the
    // contract here is "the option is plumbed through to Node and not
    // rejected as an invalid arg".
    let temp = std::env::temp_dir().join(format!(
        "pw-rust-artifacts-{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    std::fs::create_dir_all(&temp).expect("Failed to create temp artifacts dir");

    let options = LaunchOptions::new().artifacts_dir(temp.to_string_lossy().into_owned());
    let browser = pw
        .chromium()
        .launch_with_options(options)
        .await
        .expect("Failed to launch chromium with artifacts_dir");

    // Sanity-check we can do a basic operation with the resulting browser.
    let context = browser
        .new_context()
        .await
        .expect("Failed to create context");
    let page = context.new_page().await.expect("Failed to create page");
    page.goto(
        "data:text/html,<html><body>artifacts test</body></html>",
        None,
    )
    .await
    .expect("Failed to navigate");

    browser.close().await.expect("Failed to close browser");

    // Cleanup the directory; ignore errors (Playwright may have left files).
    let _ = std::fs::remove_dir_all(&temp);
}