Skip to content

Web Automation

/automate writes tests into the harness the project already has. Set one up first with /tests browser — see Testing Setup.

Syntaxis never migrates a project from one runner to another as a side effect of automating a case.

A case is automated when all of these hold:

  • its behaviour is stable — a flow still being redesigned churns the test
  • it is deterministic — same input, same observable outcome
  • it will repeat — as a regression guard or across platforms
  • the harness can actually observe its expected result

Cases stay manual, and are reported as such, when they need visual judgement, a real payment or third-party account, or a browser permission dialog the runner cannot reach.

This is the single biggest cause of a suite people stop trusting. In priority order:

  1. Role and accessible namegetByRole("button", { name: "Checkout" }). It breaks when the user-visible contract breaks, which is correct, and it fails when a control is inaccessible, which is a real defect.
  2. Label, placeholder, or visible text for controls without a clear role.
  3. A dedicated test attribute such as data-testid when nothing stable exists. Adding it to the application is a small legitimate change.
  4. Never CSS structure, generated class names, nth-child, or DOM XPath. Those encode layout, and layout is meant to change.

The default for a new harness.

import { expect, test } from "@playwright/test";
test("TC-014 checkout rejects an expired card", async ({ page }) => {
await page.goto("/cart");
await page.getByRole("button", { name: "Checkout" }).click();
await page.getByRole("radio", { name: /ending 4242/ }).check();
await page.getByRole("button", { name: "Place order" }).click();
// The expected result, both halves.
await expect(page.getByRole("alert")).toContainText("expired");
await expect(page.getByRole("listitem")).toHaveCount(1);
});
  • expect auto-waits and retries until its timeout. page.waitForTimeout(...) is the fixed sleep Syntaxis forbids.
  • Reuse authentication through a storage-state setup project, and keep the state file out of Git.
  • The case’s Platforms field decides which browsers it needs. Three engines do not run by default when the plan names one.

Used when the project already has it.

it("TC-014 rejects an expired card", () => {
cy.visit("/cart");
cy.findByRole("button", { name: "Checkout" }).click();
cy.findByRole("radio", { name: /ending 4242/ }).check();
cy.findByRole("button", { name: "Place order" }).click();
cy.findByRole("alert").should("contain.text", "expired");
cy.findByRole("listitem").should("have.length", 1);
});

Cypress cannot cross origins in one test without cy.origin. A case that routes through an external identity provider may have to stay manual, and that is said plainly rather than weakening the case.

Used when the project already has it — often because the same harness also drives mobile through Appium. One harness and one reporting format across web and device is a real reason to reuse it, but not a reason to migrate to it.

  • Assert the full expected result, including its negative half.
  • Reach the state the way a user would. Seeding through the database skips the code the case exists to exercise.
  • Never add a fixed sleep, and never add a retry to mask non-determinism. A flaky test is not a passing test.
  • Keep tests independent. Each creates and cleans up its own data; a suite that only passes in order is already broken.
  • Never commit credentials. Read them from environment variables and document the names.

Shared setup goes in the harness’s existing fixture layer. Syntaxis does not build a page-object framework or a custom assertion library unless the project already has one.

Required before any test is reported as covering a case:

  1. Invert the assertion.
  2. Run that single test and confirm it fails for the expected reason — not from a selector error or a timeout.
  3. Restore it and confirm it passes.

A test that fails with “element not found” during this check was asserting on a selector that never matched, and never covered the case at all.

Traces, videos, screenshots, reports, and stored authentication stay out of Git.

Browser suites are not added to the project’s Verify command or a GitHub workflow without a separate request. That gate belongs to /ci.