Skip to main content
Patrol provides comprehensive support for testing Flutter web applications using Playwright, a powerful browser automation framework. This enables you to write cross-platform tests that work on both mobile and web.

How It Works

When running Patrol tests on web, the following architecture is used:
1

Flutter Web Server

Patrol starts a development server that serves your Flutter web application.
2

Playwright Test Runner

Patrol automatically launches Chromium using Playwright to load your app.
3

Platform Actions Bridge

For platform automation calls (like $.platform.web.*), your Dart test code sends requests to Playwright, which executes browser-specific actions.
4

Results Collection

Test results are collected and reported back in Patrol’s standard format.
This architecture allows you to write the same Patrol tests that work across mobile and web platforms, with Playwright handling browser-specific interactions.

Prerequisites

Before running Patrol tests on web:
1

Install Node.js

Playwright requires Node.js. Install it from nodejs.org.Verify installation:
node --version
npm --version
2

First Run Setup

On first run, Patrol automatically installs:
  • Node.js dependencies
  • Playwright package
  • Chromium browser binaries
This setup happens automatically the first time you run patrol test --device chrome. It may take a moment.

Running Tests

Basic Usage

Run your Patrol tests on Chrome:
patrol test --device chrome --target patrol_test/login_test.dart
On first run, Patrol will install Playwright and browser binaries. This is a one-time setup.

Headless Mode

By default, Patrol runs web tests in headed mode (visible browser). For CI/CD pipelines, use headless mode:
patrol test \
  --device chrome \
  --target patrol_test/login_test.dart \
  --web-headless true
CI environments typically don’t provide a graphical display. Always use --web-headless true in CI pipelines.

Running All Tests

Run all web tests in your project:
patrol test --device chrome

CI/CD Integration

GitHub Actions

.github/workflows/test.yml
name: Patrol Web Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'
      
      - uses: subosito/flutter-action@v2
        with:
          channel: 'stable'
      
      - name: Install Patrol CLI
        run: flutter pub global activate patrol_cli
      
      - name: Run web tests
        run: |
          patrol test \
            --device chrome \
            --web-headless true

GitLab CI

.gitlab-ci.yml
test:web:
  image: ghcr.io/cirruslabs/flutter:stable
  
  before_script:
    - flutter pub global activate patrol_cli
    - export PATH="$PATH:$HOME/.pub-cache/bin"
  
  script:
    - patrol test --device chrome --web-headless true
Ensure your CI environment has Node.js installed. Most CI images include it by default.

Web-Specific Features

Patrol provides comprehensive browser automation through $.platform.web:

Dark Mode

// Enable dark mode
await $.platform.web.enableDarkMode();

// Disable dark mode
await $.platform.web.disableDarkMode();

Keyboard Input

// Press a single key
await $.platform.web.pressKey(key: 'Enter');
await $.platform.web.pressKey(key: 'Escape');
await $.platform.web.pressKey(key: 'a');

// Press key combinations
await $.platform.web.pressKeyCombo(keys: ['Control', 'a']); // Select all
await $.platform.web.pressKeyCombo(keys: ['Control', 'c']); // Copy
await $.platform.web.pressKeyCombo(keys: ['Control', 'v']); // Paste
await $.platform.web.pressKeyCombo(keys: ['Meta', 's']);    // Save (Cmd+S on Mac)

Permissions

// Grant browser permissions
await $.platform.web.grantPermissions(
  permissions: ['clipboard-read', 'clipboard-write'],
);

// Available permissions:
// - 'clipboard-read'
// - 'clipboard-write'
// - 'geolocation'
// - 'notifications'
// - 'camera'
// - 'microphone'

// Clear all permissions
await $.platform.web.clearPermissions();

Clipboard

// Set clipboard content
await $.platform.web.setClipboard(text: 'Hello, Patrol!');

// Read clipboard content
final clipboardText = await $.platform.web.getClipboard();
expect(clipboardText, 'Hello, Patrol!');

Browser Navigation

// Navigate back
await $.platform.web.goBack();

// Navigate forward
await $.platform.web.goForward();

Cookies

// Add a cookie
await $.platform.web.addCookie(
  name: 'session_id',
  value: 'abc123',
  url: 'http://localhost:8080',
);

// Get all cookies
final cookies = await $.platform.web.getCookies();
for (final cookie in cookies) {
  print('${cookie.name}: ${cookie.value}');
}

// Clear all cookies
await $.platform.web.clearCookies();

File Operations

Upload Files

import 'dart:convert';

// Create file data
final fileContent = utf8.encode('Hello from Patrol!');
final file = UploadFileData(
  name: 'test_file.txt',
  content: fileContent,
  mimeType: 'text/plain',
);

// Upload single file
await $.platform.web.uploadFile(files: [file]);

// Upload multiple files
final files = [
  UploadFileData(
    name: 'file1.txt',
    content: utf8.encode('Content 1'),
    mimeType: 'text/plain',
  ),
  UploadFileData(
    name: 'file2.txt',
    content: utf8.encode('Content 2'),
    mimeType: 'text/plain',
  ),
];
await $.platform.web.uploadFile(files: files);

Verify Downloads

// Trigger download in your app
await $('Download Report').tap();

// Verify files were downloaded
final downloads = await $.platform.web.verifyFileDownloads();
expect(downloads, isNotEmpty);
expect(downloads.first.suggestedFilename, 'report.pdf');

Dialog Handling

Dialogs block JavaScript execution. You must subscribe to dialog events before triggering the dialog.
// Accept alert/confirm dialog
final dialogFuture = $.platform.web.acceptNextDialog();
await $('Show Alert').tap(); // Trigger the dialog
final dialogText = await dialogFuture;
expect(dialogText, 'Are you sure?');

// Dismiss dialog
final dialogFuture = $.platform.web.dismissNextDialog();
await $('Show Confirm').tap();
final dialogText = await dialogFuture;
expect(dialogText, 'Delete this item?');

Web Element Interactions

For external HTML content (like iframes):
// Scroll to element in iframe
await $.platform.web.scrollToWeb(
  selector: '#email-input',
  iframeSelector: '#payment-iframe',
);

// Enter text in iframe input
await $.platform.web.enterTextWeb(
  selector: '#email-input',
  text: '[email protected]',
  iframeSelector: '#payment-iframe',
);

// Click button in iframe
await $.platform.web.tapWeb(
  selector: '#submit-button',
  iframeSelector: '#payment-iframe',
);

// For non-iframe elements, omit iframeSelector
await $.platform.web.tapWeb(selector: '#external-button');

Window Operations

// Resize browser window
await $.platform.web.resizeWindow(size: Size(1920, 1080));
await $.platform.web.resizeWindow(size: Size(375, 667)); // Mobile size

Complete Example

Here’s a comprehensive example using multiple web features:
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';

void main() {
  patrolTest(
    'complete web test example',
    ($) async {
      await $.pumpWidgetAndSettle(const MyApp());
      
      // Test dark mode
      await $.platform.web.enableDarkMode();
      await $.pumpAndSettle();
      expect($(#darkModeIndicator), findsOneWidget);
      
      // Test clipboard operations
      await $.platform.web.grantPermissions(
        permissions: ['clipboard-read', 'clipboard-write'],
      );
      await $.platform.web.setClipboard(text: '[email protected]');
      await $('Paste Email').tap();
      
      final clipboard = await $.platform.web.getClipboard();
      expect(clipboard, '[email protected]');
      
      // Test keyboard shortcuts
      await $.platform.web.pressKeyCombo(keys: ['Control', 'a']);
      await $.platform.web.pressKey(key: 'Delete');
      
      // Test file upload
      await $('Upload File').tap();
      final file = UploadFileData(
        name: 'document.txt',
        content: utf8.encode('Test document'),
        mimeType: 'text/plain',
      );
      await $.platform.web.uploadFile(files: [file]);
      
      // Test dialog handling
      final dialogFuture = $.platform.web.acceptNextDialog();
      await $('Delete Account').tap();
      final message = await dialogFuture;
      expect(message, contains('confirm'));
      
      // Test browser navigation
      await $('Go Back').tap();
      await $.platform.web.goBack();
    },
  );
}

Capabilities

Web platform features available through $.platform.web:
FeatureStatus
Dark mode toggle
Keyboard input
Key combinations
Permissions management
Clipboard operations
Browser navigation
Cookie management
File uploads
File download verification
Dialog handling
Iframe interactions
Window resizing
External element tapping
External text input
For a complete feature parity comparison across all platforms, see the Feature Parity page.

Limitations

Browser Support

Currently, Patrol web tests only support Chromium (Chrome). Firefox and Safari support may be added in future releases.

Mobile Browser Testing

Patrol web testing is designed for desktop browsers. For mobile browser testing, use actual mobile devices with Android/iOS platforms.

Dialog Timing

Always subscribe to dialog events before triggering them:
// ✅ Correct
final future = $.platform.web.acceptNextDialog();
await $('Show Dialog').tap();
await future;

// ❌ Wrong - dialog already shown
await $('Show Dialog').tap();
await $.platform.web.acceptNextDialog(); // Too late!

File System Access

Uploaded files and downloads are handled through Playwright’s APIs. Direct file system access from the Flutter app is limited by browser security.

Troubleshooting

Playwright Installation Fails

If automatic installation fails:
# Navigate to Patrol's web runner directory
cd ~/.patrol/web_runner  # or your custom location

# Install manually
npm install
npx playwright install chromium

Tests Fail in Headless Mode

Some visual tests may behave differently in headless mode:
# Run in headed mode for debugging
patrol test --device chrome --web-headless false

Port Already in Use

If the web server port is occupied:
# Kill process using port 8080
lsof -ti:8080 | xargs kill -9  # macOS/Linux

# Or specify a different port
patrol test --device chrome --web-port 8081

Node.js Not Found

Ensure Node.js is in your PATH:
# Check Node.js
which node
node --version

# Add to PATH if needed (add to ~/.bashrc or ~/.zshrc)
export PATH="/usr/local/bin:$PATH"

Next Steps

Feature Parity

See all available web features

Write Your First Test

Start writing Patrol tests

CI Integration

Run tests in CI/CD pipelines

Android Platform

Testing on Android devices

Build docs developers (and LLMs) love