How to Test Login Flow: A Complete Guide
How to Test Login Flow: A Complete Guide
How to Test Login Flow: A Complete Guide
Testing the login flow is a critical quality gate for any application because it is the first point of interaction where users establish trust, grant access to personal data, and initiate revenue‑generating actions. A broken login experience can instantly erode conversion, increase support load, and expose security weaknesses that attackers exploit. This guide provides a platform‑agnostic, end‑to‑end view of what to test, how to test it manually and with automation, where autonomous exploration adds value, and how to lock down the flow in production. Every section contains concrete examples, tables, and snippets you can copy into your own projects.
How to Test Login Flow: A Complete Guide – Why It Matters and What Breaks
Impact on user trust and conversion
When a user lands on a login screen, expectations are immediate: the form should load quickly, fields should accept input without unexpected restrictions, and submission should either grant access or return a clear, actionable error. Studies show that a 2‑second delay in login response can drop conversion by up to 15 % and increase abandonment. Beyond performance, any perceived insecurity—such as a password shown in plain text or a vague “invalid credentials” message that leaks whether an email exists—drives users away. Therefore, testing login is not merely a functional check; it directly influences business metrics and brand reputation.
Common failure modes
Login failures cluster into four categories:
| Category | Typical symptom | Root cause |
|---|---|---|
| Credential handling | Accepts spaces, trims incorrectly, rejects valid special characters | Improper regex validation or over‑zealous sanitization |
| UI interaction | Button stays disabled after valid input, focus traps, missing ARIA labels | Faulty state machine or inaccessible markup |
| Network / backend | 500 error after submit, intermittent timeouts, missing CSRF token | Misconfigured API gateway, race conditions in token service |
| Security | SQL injection via username, password leaked in URL, brute‑force without throttling | Insufficient input sanitization, missing rate‑limit, insecure token storage |
Each of these can be reproduced with a targeted test case, which we enumerate in the next section.
How to Test Login Flow: A Complete Guide – Building a Comprehensive Test Matrix
A test matrix ensures you cover happy paths, error paths, edge cases, accessibility, security, and production‑only nuances. The table below lists representative test IDs, a short description, the expected outcome, priority (P0 = blocker, P1 = high, P2 = medium), and a note on whether the case is readily automatable.
| Test ID | Description | Expected Result | Priority | Automation Feasibility |
|---|---|---|---|---|
| L‑001 | Valid username + correct password → submit | Successful authentication, redirect to landing page, auth token set | P0 | High (UI or API) |
| L‑002 | Valid username + incorrect password → submit | Inline error “Invalid password”, field retains focus, no token | P0 | High |
| L‑003 | Non‑existent email + any password → submit | Generic error “Invalid credentials” (no user enumeration) | P1 | High |
| L‑004 | Empty username field → submit | Validation message “Username is required”, focus on username | P1 | High |
| L‑005 | Username with leading/trailing spaces → submit | System trims spaces and authenticates if core value matches | P2 | Medium (needs explicit trim check) |
| L‑006 | Username containing Unicode emoji (😀) → submit | Rejected with “Invalid characters” or accepted if allowed by spec | P2 | Medium |
| L‑007 | Password with SQL injection pattern (‘ OR ‘1’='1) → submit | Rejected, no database error exposed | P1 | High (API) |
| L‑008 | Password with XSS vector () → submit | Rejected or safely escaped; no script execution in response | P1 | High |
| L‑009 | Rapid fire login attempts (10 req/sec) → submit | Rate limit triggered after threshold, returns 429 or temporary lock | P1 | High (needs load generator) |
| L‑010 | Account locked after 5 failed attempts → submit on 6th attempt | Error “Account locked, try again later” or unlock via email link | P1 | Medium (requires admin API to reset) |
| L‑011 | Successful login, then back button → navigate | Session persists, no re‑login prompt unless explicit logout | P2 | High |
| L‑012 | Login page loaded with screen reader (NVDA) → navigate | All fields labeled, error messages announced, logical tab order | P1 | Medium (requires aXe or similar) |
| L‑013 | Login page contrast ratio (WCAG AA) → verify | Minimum 4.5:1 for normal text, 3:1 for large text | P1 | Low (manual or automated contrast tool) |
| L‑014 | Login via third‑party IDP (Google OAuth) → submit | Redirect to IDP, consent screen, return with valid token | P1 | High (mock IDP) |
| L‑015 | Feature flag enabling passwordless login → submit with email only | Magic link sent, clicking link logs in user | P2 | Medium (requires email trap) |
| L‑016 | Login under poor network (3G simulated) → submit | Request retries, eventual success or clear timeout message | P2 | Medium (network throttling) |
| L‑017 | Login with autopopulated credentials from password manager → submit | Fields filled, submission works, no double‑entry glitch | P2 | Low (depends on manager integration) |
| L‑018 | Login page after session expiry → submit | Redirect to login, no stale data displayed | P1 | High |
| L‑019 | Login with disabled JavaScript → submit (if fallback exists) | Server‑side validation works, same UX as JS version | P2 | Low (requires noscript testing) |
| L‑020 | Login with biometric prompt (native app) → cancel | Falls back to manual credential entry | P2 | Medium (requires device farm) |
How to use the matrix
- Prioritize P0 and P1 cases for every release.
- Automate all high‑feasibility items; reserve low‑feasibility for exploratory manual sessions or specialized tools (e.g., contrast analyzers).
- Update the matrix whenever the authentication contract changes (new IDP, MFA method, password policy).
How to Test Login Flow: A Complete Guide – Manual Testing Approaches
Exploratory testing session outline
A 45‑minute exploratory session can uncover issues that scripted checks miss. Begin with a charter: “Investigate login flow for edge‑case input, accessibility, and security under realistic user conditions.” Follow this loose script:
- Load the page on a clean browser profile (no extensions, cache cleared).
- Observe initial state: note loading spinners, placeholder text, focus state.
- Keyboard‑only navigation: Tab through all controls, verify visible focus rings, ensure Enter submits.
- Screen reader pass: Enable NVDA or VoiceOver, listen for announcements on each field and error messages.
- Invalid data matrix: Try the combinations from L‑002 through L‑007, noting any inconsistency in error text or field retention.
- Whitespace and Unicode: Paste strings with leading/trailing spaces, zero‑width joiner, emoji, and observe trim behavior.
- Brute‑force simulation: Use the browser’s console to fire rapid fetch requests to the login endpoint, watch for 429 responses or lockout messages.
- Third‑party login: Click the Google/Facebook button, follow the OAuth flow, verify token exchange and return to the app.
- Network throttling: Enable Chrome DevTools → Network → throttling to Slow 3G, repeat a successful login, check for retry UI.
- Session persistence: Log in, refresh the page, close and reopen the browser, confirm you remain authenticated unless explicit logout.
- Accessibility audit: Run axe‑core manually, record any violations of WCAG 2.1 AA.
- Security sniffing: Enable OWASP ZAP as a proxy, attempt a simple SQLi payload in the username field, inspect the response for error leakage.
At the end, log any anomalies in a shared spreadsheet with steps, screenshots, and severity.
Checklist for manual testers
Copy this checklist into your test management tool; each item can be ticked off during a session.
- [ ] Page loads within 2 s on 3G simulated connection
- [ ] All input fields have associated
oraria-label - [ ] Error messages are announced by screen readers and visible in contrast‑checked colors
- [ ] Valid credentials lead to redirect and presence of auth token in storage
- [ ] Invalid credentials produce generic message, no field clearing
- [ ] Leading/trailing whitespace is trimmed before validation
- [ ] Unicode characters are handled per spec (either rejected or accepted)
- [ ] SQLi and XSS payloads are sanitized, no error details exposed
- [ ] Rate limiting engages after configured threshold (visible 429 or lockout)
- [ ] Account lockout triggers appropriate unlock flow (email link, admin reset)
- [ ] Third‑party IDP login completes and returns a valid token
- [ ] Feature‑flagged passwordless path sends magic link and logs in on click
- [ ] Login works with password manager autofill without duplication
- [ ] No sensitive data (password, token) appears in URL or browser devtools network tab in plain text
- [ ] JavaScript disabled fallback (if any) still validates and logs in
Tools that amplify manual effort
- Browser DevTools – Network tab for inspecting request/response headers, disabling cache, throttling.
- Charles Proxy / Mitmproxy – View and modify HTTP traffic in real time, useful for testing token injection or simulating server errors.
- axe‑core – Browser extension for instant WCAG violation reports.
- OWASP ZAP – Active scanner to try common injection payloads against the login endpoint.
- Postman – Quick ad‑hoc API calls to verify backend behavior without UI.
Example manual test script (step‑by‑step)
Below is a concise, reproducible script you can hand to a junior tester for the “valid credentials + whitespace” case (L‑005).
1. Open Chrome incognito window.
2. Navigate to https://app.example.com/login.
3. Wait for the username field to receive focus (visual cue: blinking cursor).
4. Type " user123 " (three spaces before and after the username).
5. Press Tab to move to password field.
6. Type "CorrectPass!23".
7. Press Enter or click the Login button.
8. Verify:
a. URL changes to https://app.example.com/dashboard.
b. Browser storage (local/session) contains a token named "access_token".
c. No error toast appears.
9. Optional: Open DevTools → Application → Local Storage and confirm token value is a JWT.
How to Test Login Flow: A Complete Guide – Automated Testing Strategies
Choosing the right layer (unit, API, UI)
A robust automation strategy layers tests:
- Unit tests validate pure functions such as password‑strength rules, email regex, and token generation logic.
- API tests hit the authentication endpoint directly, bypassing UI quirks, and are ideal for data‑driven validation of error codes, rate limits, and token payloads.
- UI tests (Selenium, Playwright, Cypress) confirm that the assembled page behaves correctly for real users, covering focus management, accessibility attributes, and client‑side state transitions.
Start with unit and API tests for fast feedback; add UI tests for critical paths that involve third‑party widgets (e.g., Google Sign‑In button) or complex client‑side validation.
Sample code for API validation (using curl + jq)
The following Bash snippet checks the happy path and two error cases against a mock login API. Adjust $BASE_URL, $USERNAME, and $PASSWORD as needed.
#!/usr/bin/env bash
BASE_URL="https://api.example.com/auth"
USERNAME="tester@example.com"
PASSWORD="StrongPass!23"
# Happy path
echo "=== Happy path ==="
RESPONSE=$(curl -s -X POST "$BASE_URL/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}")
echo "$RESPONSE" | jq .
TOKEN=$(echo "$RESPONSE" | jq -r .access_token)
if [[ -n "$TOKEN" && "$TOKEN" != "null" ]]; then
echo "✅ Token received"
else
echo "❌ Missing token"
fi
# Invalid password
echo -e "\n=== Invalid password ==="
RESPONSE=$(curl -s -X POST "$BASE_URL/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\",\"password\":\"wrong\"}")
echo "$RESPONSE" | jq .
MSG=$(echo "$RESPONSE" | jq -r .message)
if [[ "$MSG" == *"Invalid credentials"* ]]; then
echo "✅ Correct error message"
else
echo "❌ Unexpected message: $MSG"
fi
# Rate limit simulation (5 rapid requests)
echo -e "\n=== Rate limit test ==="
for i in {1..5}; do
curl -s -o /dev/null -w "%{http_code}\n" -X POST "$BASE_URL/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\",\"password\":\"wrong\"}" &
done
wait
echo "Check server logs for 429 responses"
This script can be dropped into a CI job; non‑zero exit codes from jq parsing or missing token trigger a failure.
Sample UI automation with Selenium/WebDriver (Java)
Below is a JUnit 5 test that validates the login page’s focus order and error handling using Selenium 4.
package com.example.tests;
import org.junit.jupiter.api.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.*;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;
class LoginFlowTest {
private WebDriver driver;
private WebDriverWait wait;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
driver.get("https://app.example.com/login");
}
@AfterEach
void tearDown() {
if (driver != null) driver.quit();
}
@Test
void validLoginRedirectsAndSetsToken() {
// Locate fields
WebElement username = driver.findElement(By.id("username"));
WebElement password = driver.findElement(By.id("password"));
WebElement loginBtn = driver.findElement(By.cssSelector("button[type='submit']"));
// Enter credentials
username.sendKeys("qa_user");
password.sendKeys("QaPass!2024");
loginBtn.click();
// Wait for redirect to dashboard
wait.until(ExpectedConditions.urlContains("/dashboard"));
assertTrue(driver.getCurrentUrl().contains("/dashboard"),
"Expected redirect to dashboard after login");
// Verify token in local storage (example for SPA)
String token = (String) ((JavascriptExecutor) driver)
.executeScript("return window.localStorage.getItem('access_token');");
assertNotNull(token, "Access token should be stored");
assertFalse(token.isEmpty(), "Token must not be empty");
}
@Test
void invalidPasswordShowsInlineError() {
WebElement username = driver.findElement(By.id("username"));
WebElement password = driver.findElement(By.id("password"));
WebElement loginBtn = driver.findElement(By.cssSelector("button[type='submit']"));
WebElement errorMsg = driver.findElement(By.id("password-error"));
username.sendKeys("qa_user");
password.sendKeys("wrong");
loginBtn.click();
// Error should appear within 2 seconds
wait.until(ExpectedConditions.visibilityOf(errorMsg));
assertTrue(errorMsg.isDisplayed(), "Error message should be visible");
assertEquals("Invalid password", errorMsg.getText().trim(),
"Error text mismatch");
}
@Test
void focusOrderIsLogical() {
WebElement username = driver.findElement(By.id("username"));
WebElement password = driver.findElement(By.id("password"));
WebElement loginBtn = driver.findElement(By.cssSelector("button[type='submit']"));
// Simulate Tab navigation
username.sendKeys(Keys.TAB);
assertEquals(password, driver.switchTo().activeElement(),
"Focus should move from username to password on Tab");
password.sendKeys(Keys.TAB);
assertEquals(loginBtn, driver.switchTo().activeElement(),
"Focus should move from password to login button on Tab");
}
}
Why this works
- Uses explicit waits (
WebDriverWait) to avoid flaky timing issues. - Checks both UI state (URL, error message) and client‑side storage (token).
- Includes a focus‑order test that catches regressions in
tabindexor dynamic DOM insertion.
Sample UI automation with Playwright (TypeScript)
Playwright offers auto‑waiting and built‑in tracing, which reduces boilerplate.
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test('successful login redirects and sets token', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.fill('#username', 'qa_user');
await page.fill('#password', 'QaPass!2024');
await page.click('button[type=submit]');
// Wait for navigation
await expect(page).toHaveURL(/.*\/dashboard/);
// Verify token in local storage
const token = await page.evaluate(() => window.localStorage.getItem('access_token'));
expect(token).toBeTruthy();
expect(token.length).toBeGreaterThan(0);
});
test('invalid password shows inline error', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.fill('#username', 'qa_user');
await page.fill('#password', 'wrong');
await page.click('button[type=submit]');
const error = page.locator('#password-error');
await expect(error).toBeVisible();
await expect(error).toHaveText('Invalid password');
});
test('username trims whitespace', async ({ page }) => {
await page.goto('https://app.example.com/login');
await page.fill('#username', ' qa_user ');
await page.fill('#password', 'QaPass!2024');
await page.click('button[type=submit]');
await expect(page).toHaveURL(/.*\/dashboard/);
// Optional: confirm that the stored username lacks spaces
const stored = await page.evaluate(() => window.localStorage.getItem('lastUsername'));
expect(stored).toBe('qa_user');
});
});
Run with npx playwright test; the generated playwright-report folder contains screenshots and traces for any failure.
Data‑driven approach with CSV/JSON
Externalizing test data lets you expand the matrix without touching code. Example using TestNG and a CSV file (login-data.csv):
username,password,expectedOutcome,description
qa_user,QaPass!2024,SUCCESS,Valid credentials
qa_user,wrongpass,ERROR_INVALID_PASSWORD,Incorrect password
nonexist@domain.com,any,ERROR_GENERIC,Non‑existent user
spaced ,QaPass!2024,SUCCESS,Leading/trailing spaces trimmed
TestNG @DataProvider reads the file and feeds each row to a test method, asserting the observed outcome against expectedOutcome.
Handling CAPTCHA and 2FA in test environments
Real‑world login often includes secondary challenges that would block automation. Strategies:
- Feature flag to disable CAPTCHA for internal environments (e.g.,
FEATURE_CAPTCHA_ENABLED=false). - Test‑only bypass endpoint that returns a fixed 2FA code when a special header (
X-Test-Mode: true) is present. - Mock SMS/email service (like Mailosaur) that captures the OTP and makes it available via API for the test to retrieve and submit.
Never ship these bypasses to production; gate them behind environment‑specific configuration.
Parallel execution and flakiness mitigation
- Split UI tests across multiple containers or VMs using Selenium Grid or Playwright’s
--workersflag. - Use test retries (e.g., JUnit’s
@Retryor Playwright’stest.describe.configure({ retries: 2 })) only for known flaky network‑dependent steps. - Capture video and trace on failure; review them to distinguish genuine bugs from environment glitches.
- Enforce deterministic test data: create a fresh user via API before each test, delete after, or use UUID‑based usernames to avoid collisions.
How to Test Login Flow: A Complete Guide – Leveraging Autonomous, Persona‑Driven Exploration
How SUSA’s autonomous agent works (no scripts)
SUSA ingests an APK or a web URL, then launches a headless browser (or device emulator) that autonomously explores the application. It does not rely on pre‑written test scripts; instead, it builds a state graph of screens, actions, and outcomes as it interacts. The agent applies heuristics for tapping, scrolling, typing, and handling dialogs, guided by a set of persona profiles that dictate behavior patterns (e.g., an “impatient” persona types quickly and abandons slow fields, while an “elderly” persona prefers larger tap targets and uses accessibility features).
During exploration, the agent automatically:
- Detects crashes, ANRs, and JavaScript exceptions.
- Flags accessibility violations via integrated axe‑core rules.
- Attempts common security payloads (SQLi, XSS, path traversal) in input fields.
- Records every navigation path and marks dead ends (e.g., a button that leads to a blank screen).
- Generates regression scripts in Appium (Android) or Playwright (Web) for any flow it successfully completes, complete with assertions on HTTP status codes and UI elements.
Because the agent learns from each run, subsequent executions prioritize unexplored branches and previously failing actions, increasing coverage without manual test maintenance.
Persona profiles and their behavior patterns
SUSA ships with eight built‑in personas, each tuned to a distinct interaction style. For login flow testing, the following are especially relevant:
| Persona | Key traits | What it reveals about login |
|---|---|---|
| Curious | Tries every link, opens dev tools, experiments with unusual characters | Finds hidden “forgot password” links, discovers if special‑character usernames are silently rejected |
| Impatient | Types rapidly, aborts if a field takes >1 s to expose suggestions, taps multiple times | Exposes race conditions where rapid taps cause double submission or disabled button states |
| Novice | Relies on placeholders, avoids keyboard shortcuts, reads error messages literally | Highlights vague error copy, missing inline validation, reliance on tooltip-only hints |
| Adversarial | Actively attempts injection, overflow, and session‑fixation payloads | Uncovers insufficient sanitization, lack of rate limiting, token leakage in URL |
| Elderly | Prefers larger tap targets, uses screen reader, disables JavaScript when possible | Checks WCAG contrast, focus order, and graceful degradation when JS is off |
| Accessibility | Navigates solely via keyboard and screen reader, expects ARIA labels | Validates that all form controls are properly labeled and that live regions announce errors |
| Power user | Uses password manager autofill, expects “remember me” toggle, tests shortcut keys | Verifies that autofill does not cause duplicate entry, that “remember me” persists correctly |
| Security‑conscious | Looks for password reuse warnings, checks for HTTPS, inspects network | Detects mixed‑content issues, missing HSTS, absence of secure flag on cookies |
When the agent runs with these personas, it often surfaces bugs that a single‑scripted test would never encounter because the script follows a deterministic path.
Real‑world bug examples caught only by persona‑driven runs
- Impersonation via rapid double‑tap – The impatient persona tapped the login button twice before the first request completed, causing the backend to create two concurrent sessions and exposing a session‑fixation vulnerability. Scripted tests that used a single click never reproduced the race.
- Screen‑reader announcement missing for password error – The accessibility persona navigated with TalkBack; the error message appeared visually but was not placed in an
aria-liveregion, so the user received no auditory feedback. Automated UI tests that only asserted the presence of a DOM element missed this because the element existed but was not live. - Unicode username normalization bug – The curious persona pasted an emoji‑laden username (
😀alice😀). The frontend trimmed the emojis but the backend stored the raw string, leading to a mismatch when the token was later validated. Unit tests that limited input to ASCII never caught the discrepancy. - Password manager autofill duplication – The power‑user persona triggered Chrome’s autofill, which filled both username and password fields, then manually typed the password again, resulting in a duplicated string (“PassPass”). The login endpoint rejected the malformed credential, but only the power‑user’s exploratory path revealed the UI glitch.
These findings demonstrate the complementary value of autonomous, persona‑driven testing alongside traditional scripted suites.
Cross‑session learning and regression script generation
After each execution, SUSA stores a JSON representation of the explored state graph. On the next run, it:
- Skips already‑traversed successful paths unless a change in the underlying code is detected (via checksum of DOM or API contract).
- Prioritizes edges that previously led to errors or dead ends, increasing the chance of regression detection.
- Automatically exports a Playwright script for any successful flow it discovered, complete with assertions on HTTP status, response body schema, and UI elements.
This means that over time, the regression suite grows organically, covering scenarios that a human might never think to script, while maintaining a low maintenance overhead because the scripts are regenerated when the underlying UI changes.
How to Test Login Flow: A Complete Guide – Setting Up a CI Pipeline for Login Tests
Triggering on PR and nightly
A robust CI strategy runs fast unit and API tests on every pull request, while reserving heavier UI and exploratory runs for nightly builds or release branches. Example using GitHub Actions:
name: Login Flow CI
on:
pull_request:
branches: [ main ]
schedule:
- cron: '0 2 * * *' # Every night at 02:00 UTC
jobs:
unit-and-api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm test # runs Jest unit + API tests (fast)
- name: Upload coverage
uses: actions/upload-artifact@v3
with:
name: coverage
path: coverage/
ui-playwright:
needs: unit-and-api
runs-on: ubuntu-latest
if: github.event_name == 'schedule' || github.event.pull_request.head.repo.fork == false
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run UI tests
run: npx playwright test --reporter=html
- name: Upload Playwright report
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
autonomous-exploration:
needs: unit-and-api
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v3
- name: Install SUSA agent
run: pip install susatest-agent
- name: Run SUSA against staging URL
env:
SUSA_API_KEY: ${{ secrets.SUSA_KEY }}
run: |
susatest-agent explore \
--url https://staging.example.com \
--personas curious,impatient,adversarial,accessibility \
--output susa-results.json
- name: Upload SUSA results
uses: actions/upload-artifact@v3
with:
name: susa-results
path: susa-results.json
Explanation
- The
unit-and-apijob runs on every PR, providing rapid feedback. - The
ui-playwrightjob runs on PRs from trusted forks and on all scheduled nightly runs, ensuring UI validation without overloading PR pipelines. - The
autonomous-explorationjob runs only at night, consuming more time but delivering deep, persona‑driven insights.
Adjust the if conditions to match your branching model (e.g., run exploratory on release/* branches only).
Artifact collection (screenshots, logs, video)
Capture diagnostic value of a test failure is essential for rapid triage. Configure your test runners to automatically attach artifacts:
- Playwright:
test.fail(async ({ page }) => { await page.screenshot({ path:failure-${testInfo.title}.png}); await page.video().saveAs(failure-${testInfo.title}.webm); }); - Selenium: Use
TakesScreenshotinterface andEventFiringWebDriverto log screenshots on exception. - SUSA: The agent already outputs a JSON log and optionally a video of the session; ensure your CI step uploads these artifacts.
Make artifacts downloadable from the workflow run page so developers can replay the exact state that caused the failure.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free