viewpoint-test 0.4.3

Test framework for Viewpoint browser automation with Playwright-style assertions
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
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
#![cfg(feature = "integration")]

//! End-to-end tests for the complete Viewpoint test framework.
//!
//! These tests exercise the full stack: browser automation, locators,
//! actions, and assertions working together in realistic scenarios.

use std::sync::Once;
use std::time::Duration;

use viewpoint_core::DocumentLoadState;
use viewpoint_test::{TestHarness, expect, expect_page};

static TRACING_INIT: Once = Once::new();

fn init_tracing() {
    TRACING_INIT.call_once(|| {
        tracing_subscriber::fmt()
            .with_env_filter(
                tracing_subscriber::EnvFilter::from_default_env()
                    .add_directive(tracing::Level::INFO.into()),
            )
            .with_test_writer()
            .try_init()
            .ok();
    });
}

/// E2E test: Complete form interaction workflow
///
/// This test exercises:
/// - Navigation
/// - Element location with various selectors
/// - Form filling (fill, type, click)
/// - Checkbox interaction
/// - Text assertions
/// - Page assertions
#[tokio::test]
async fn e2e_form_interaction() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");
    let page = harness.page();

    // Navigate to httpbin forms page
    page.goto("https://httpbin.org/forms/post")
        .wait_until(DocumentLoadState::Load)
        .goto()
        .await
        .expect("should navigate");

    // Verify we're on the right page
    expect_page(page)
        .to_have_url_containing("httpbin.org/forms")
        .await
        .expect("should be on forms page");

    // Fill customer name using CSS selector
    let name_input = page.locator("input[name='custname']");
    expect(&name_input)
        .to_be_visible()
        .await
        .expect("name input should be visible");

    name_input.fill("John Doe").await.expect("should fill name");

    // Fill phone using type_text for character-by-character input
    let phone_input = page.locator("input[name='custtel']");
    phone_input.click().await.expect("should click phone");
    phone_input
        .type_text("555-1234")
        .await
        .expect("should type phone");

    // Fill email
    let email_input = page.locator("input[name='custemail']");
    email_input
        .fill("john@example.com")
        .await
        .expect("should fill email");

    // Select pizza size (radio buttons) - click medium
    let medium_size = page.locator("input[value='medium']");
    medium_size.click().await.expect("should select medium");

    // Check some toppings
    let cheese = page.locator("input[value='cheese']");
    cheese.check().await.expect("should check cheese");
    expect(&cheese)
        .to_be_checked()
        .await
        .expect("cheese should be checked");

    let mushrooms = page.locator("input[value='mushroom']");
    mushrooms.check().await.expect("should check mushrooms");
    expect(&mushrooms)
        .to_be_checked()
        .await
        .expect("mushrooms should be checked");

    // Uncheck mushrooms
    mushrooms.uncheck().await.expect("should uncheck mushrooms");
    expect(&mushrooms)
        .not()
        .to_be_checked()
        .await
        .expect("mushrooms should be unchecked");

    // Fill delivery time
    let time_input = page.locator("input[name='delivery']");
    time_input.fill("18:00").await.expect("should fill time");

    // Fill comments in textarea
    let comments = page.locator("textarea[name='comments']");
    comments
        .fill("Please ring the doorbell twice.")
        .await
        .expect("should fill comments");
}

/// E2E test: Navigation and content verification
///
/// This test exercises:
/// - Multiple page navigations
/// - URL assertions
/// - Title assertions  
/// - Text content assertions
/// - Element visibility
#[tokio::test]
async fn e2e_navigation_and_content() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");
    let page = harness.page();

    // Navigate to example.com
    page.goto("https://example.com")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate to example.com");

    // Verify page state
    expect_page(page)
        .to_have_title("Example Domain")
        .await
        .expect("should have correct title");

    expect_page(page)
        .to_have_url_containing("example.com")
        .await
        .expect("should have correct URL");

    // Verify heading
    let heading = page.locator("h1");
    expect(&heading)
        .to_be_visible()
        .await
        .expect("heading should be visible");

    expect(&heading)
        .to_have_text("Example Domain")
        .await
        .expect("heading should have correct text");

    // Verify paragraphs exist
    let paragraphs = page.locator("p");
    let count = paragraphs.count().await.expect("should count paragraphs");
    assert!(count >= 1, "should have at least one paragraph");

    // Navigate to httpbin
    page.goto("https://httpbin.org/html")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate to httpbin");

    expect_page(page)
        .to_have_url_containing("httpbin.org")
        .await
        .expect("should be on httpbin");

    // Verify content
    let body = page.locator("body");
    expect(&body)
        .to_contain_text("Herman Melville")
        .await
        .expect("should contain expected text");
}

/// E2E test: Element selection and chaining
///
/// This test exercises:
/// - CSS selectors
/// - Text selectors
/// - Locator chaining
/// - nth selection (first, last)
#[tokio::test]
async fn e2e_element_selection() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");
    let page = harness.page();

    page.goto("https://example.com")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate");

    // CSS selector
    let h1 = page.locator("h1");
    expect(&h1)
        .to_be_visible()
        .await
        .expect("h1 should be visible");

    // Text selector
    let by_text = page.get_by_text("Example Domain");
    expect(&by_text)
        .to_be_visible()
        .await
        .expect("text element should be visible");

    // Chained locator
    let chained = page.locator("body").locator("div").locator("h1");
    expect(&chained)
        .to_be_visible()
        .await
        .expect("chained locator should find element");

    // First/last selection
    let first_p = page.locator("p").first();
    expect(&first_p)
        .to_be_visible()
        .await
        .expect("first paragraph should be visible");
}

/// E2E test: Mouse interactions
///
/// This test exercises:
/// - Click
/// - Double-click
/// - Hover
#[tokio::test]
async fn e2e_mouse_interactions() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");
    let page = harness.page();

    page.goto("https://example.com")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate");

    // Hover over heading
    let heading = page.locator("h1");
    heading.hover().await.expect("should hover over heading");

    // Double-click to select text
    heading
        .dblclick()
        .await
        .expect("should double-click heading");

    // Click the link
    let link = page.locator("a");
    expect(&link)
        .to_be_visible()
        .await
        .expect("link should be visible");

    // Just verify we can click (don't follow navigation)
    // link.click().await.expect("should click link");
}

/// E2E test: Keyboard interactions
///
/// This test exercises:
/// - Focus
/// - Key presses
/// - Modifier keys
#[tokio::test]
async fn e2e_keyboard_interactions() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");
    let page = harness.page();

    page.goto("https://httpbin.org/forms/post")
        .wait_until(DocumentLoadState::Load)
        .goto()
        .await
        .expect("should navigate");

    // Focus input
    let input = page.locator("input[name='custname']");
    input.focus().await.expect("should focus input");

    // Type text
    input.type_text("Test").await.expect("should type");

    // Press Tab to move to next field
    input.press("Tab").await.expect("should press Tab");

    // Select all with Ctrl+A
    let phone = page.locator("input[name='custtel']");
    phone.fill("12345").await.expect("should fill");
    phone.press("Control+a").await.expect("should select all");

    // Clear with backspace
    phone.press("Backspace").await.expect("should delete");
}

/// E2E test: Assertion negation
///
/// This test exercises:
/// - `.not()` modifier on assertions
#[tokio::test]
async fn e2e_assertion_negation() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");
    let page = harness.page();

    page.goto("https://example.com")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate");

    let heading = page.locator("h1");

    // Verify NOT having wrong text
    expect(&heading)
        .not()
        .to_have_text("Wrong Title")
        .await
        .expect("should not have wrong text");

    // Verify non-existent element is hidden
    let nonexistent = page.locator("#does-not-exist");
    expect(&nonexistent)
        .to_be_hidden()
        .await
        .expect("nonexistent should be hidden");

    expect(&nonexistent)
        .not()
        .to_be_visible()
        .await
        .expect("nonexistent should not be visible");

    // Page assertions with negation
    expect_page(page)
        .not()
        .to_have_url("https://google.com")
        .await
        .expect("should not be on google");

    expect_page(page)
        .not()
        .to_have_title("Google")
        .await
        .expect("should not have Google title");
}

/// E2E test: Custom timeouts
///
/// This test exercises:
/// - Custom assertion timeouts
/// - Fast failure on timeout
#[tokio::test]
async fn e2e_custom_timeouts() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");
    let page = harness.page();

    page.goto("https://example.com")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate");

    // Short timeout should still work for existing element
    let heading = page.locator("h1");
    expect(&heading)
        .timeout(Duration::from_millis(500))
        .to_be_visible()
        .await
        .expect("should find element quickly");

    // Very short timeout should fail for wrong text
    let result = expect(&heading)
        .timeout(Duration::from_millis(100))
        .to_have_text("Wrong Text")
        .await;

    assert!(
        result.is_err(),
        "should fail with wrong text and short timeout"
    );
}

/// E2E test: Multiple pages
///
/// This test exercises:
/// - Creating multiple pages
/// - Independent page state
#[tokio::test]
async fn e2e_multiple_pages() {
    init_tracing();

    let harness = TestHarness::new().await.expect("should create harness");

    // First page
    let page1 = harness.page();
    page1
        .goto("https://example.com")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate page 1");

    // Create second page
    let page2 = harness.new_page().await.expect("should create second page");
    page2
        .goto("https://httpbin.org/html")
        .wait_until(DocumentLoadState::DomContentLoaded)
        .goto()
        .await
        .expect("should navigate page 2");

    // Verify both pages have correct content
    expect_page(page1)
        .to_have_title("Example Domain")
        .await
        .expect("page 1 should have correct title");

    let page2_body = page2.locator("body");
    expect(&page2_body)
        .to_contain_text("Herman Melville")
        .await
        .expect("page 2 should have correct content");
}