viewpoint-test-macros

Crates.ioviewpoint-test-macros
lib.rsviewpoint-test-macros
version0.4.3
created_at2025-12-26 15:54:43.128496+00
updated_at2026-01-17 01:18:12.590903+00
descriptionProcedural macros for Viewpoint test framework
homepagehttps://github.com/stephenstubbs/viewpoint
repositoryhttps://github.com/stephenstubbs/viewpoint
max_upload_size
id2005894
size34,951
(stephenstubbs)

documentation

https://docs.rs/viewpoint-test-macros

README

viewpoint-test-macros

Crates.io Documentation License: MIT

Procedural macros for the Viewpoint test framework, providing the #[viewpoint::test] attribute macro for convenient test setup.

This crate is part of the Viewpoint browser automation framework.

Features

  • Automatic Setup: Browser, context, and page are set up before the test
  • Automatic Cleanup: Resources are cleaned up after the test completes
  • Fixture Injection: Request fixtures by parameter type (Page, BrowserContext, Browser)
  • Fixture Scoping: Share browsers/contexts across tests for performance
  • Configuration: Customize headless mode, timeouts, and more

Installation

This crate is typically used through viewpoint-test:

[dev-dependencies]
viewpoint-test = "0.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Quick Start

use viewpoint_test::{test, Page, expect};

#[viewpoint_test::test]
async fn my_test(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
    page.goto("https://example.com").goto().await?;
    
    let heading = page.locator("h1");
    expect(&heading).to_be_visible().await?;
    
    Ok(())
}

The macro automatically:

  • Creates a TestHarness
  • Provides the requested fixtures (page, context, browser)
  • Handles cleanup on test completion

Fixture Parameters

Request different fixtures by changing the parameter types:

use viewpoint_test::test;
use viewpoint_core::{Page, BrowserContext, Browser};

// Get just the page (most common)
#[viewpoint_test::test]
async fn test_with_page(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
    page.goto("https://example.com").goto().await?;
    Ok(())
}

// Get page and context
#[viewpoint_test::test]
async fn test_with_context(
    page: &Page,
    context: &BrowserContext
) -> Result<(), Box<dyn std::error::Error>> {
    // Add cookies to the context
    context.add_cookies(vec![/* ... */]).await?;
    page.goto("https://example.com").goto().await?;
    Ok(())
}

// Get page, context, and browser
#[viewpoint_test::test]
async fn test_with_browser(
    page: &Page,
    context: &BrowserContext,
    browser: &Browser
) -> Result<(), Box<dyn std::error::Error>> {
    // Create additional contexts
    let another_context = browser.new_context().await?;
    Ok(())
}

Configuration Options

Configure the test with attribute arguments:

use viewpoint_test::test;
use viewpoint_core::Page;

// Run in headed mode (visible browser)
#[viewpoint_test::test(headless = false)]
async fn headed_test(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
    page.goto("https://example.com").goto().await?;
    Ok(())
}

// Custom timeout (in milliseconds)
#[viewpoint_test::test(timeout = 60000)]
async fn slow_test(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
    // This test has a 60 second timeout
    page.goto("https://slow-site.com").goto().await?;
    Ok(())
}

Fixture Scoping

Share browsers/contexts across tests for better performance:

Browser Scope

Share a browser across multiple tests (each test gets a fresh context and page):

use viewpoint_test::test;
use viewpoint_core::{Browser, Page};
use std::sync::OnceLock;

// Define a shared browser
static BROWSER: OnceLock<Browser> = OnceLock::new();

async fn shared_browser() -> &'static Browser {
    BROWSER.get_or_init(|| {
        tokio::runtime::Handle::current().block_on(async {
            Browser::launch().headless(true).launch().await.unwrap()
        })
    })
}

#[viewpoint_test::test(scope = "browser", browser = "shared_browser")]
async fn fast_test_1(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
    // Uses shared browser, but fresh context and page
    page.goto("https://example.com").goto().await?;
    Ok(())
}

#[viewpoint_test::test(scope = "browser", browser = "shared_browser")]
async fn fast_test_2(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
    // Same shared browser, different context and page
    page.goto("https://example.org").goto().await?;
    Ok(())
}

Context Scope

Share a context across tests (each test gets a fresh page, but shares cookies/state):

use viewpoint_test::test;
use viewpoint_core::{BrowserContext, Page};

async fn shared_context() -> &'static BrowserContext {
    // Return a shared context
    todo!()
}

#[viewpoint_test::test(scope = "context", context = "shared_context")]
async fn test_with_shared_context(page: &Page) -> Result<(), Box<dyn std::error::Error>> {
    // Uses shared context (shares cookies, storage, etc.)
    page.goto("https://example.com").goto().await?;
    Ok(())
}

All Configuration Options

Option Type Default Description
headless bool true Run browser in headless mode
timeout integer 30000 Default timeout in milliseconds
scope string - Fixture scope: "browser" or "context"
browser string - Function name returning shared browser (required when scope = "browser")
context string - Function name returning shared context (required when scope = "context")

When to Use TestHarness Instead

The macro is convenient but TestHarness offers more control:

  • When you need to configure the browser context with specific options
  • When you need to set up network interception before navigation
  • When you want more explicit control over setup and teardown
  • When you need to handle setup failures differently
use viewpoint_test::TestHarness;

#[tokio::test]
async fn explicit_test() -> Result<(), Box<dyn std::error::Error>> {
    let harness = TestHarness::new().await?;
    let page = harness.page();

    // More explicit setup gives you more control
    page.goto("https://example.com").goto().await?;

    Ok(())
}

License

MIT

Commit count: 44

cargo fmt