agentleFS
grzmolgrzmol/vibe-force/skills/sf-ui-test-automation/SKILL.md

sf-ui-test-automation

Skill2 starsChanged 2 months agoMITView on GitHub

Build automated Salesforce UI tests - pick the cheapest tier that can catch the bug (Apex unit, LWC Jest, UTAM end-to-end, Agentforce agent tests), scaffold a UTAM plus WebdriverIO project with Salesforce's own page objects, authenticate a test run without committing a credential, and keep the suite deterministic in CI. Use when writing or repairing browser-level tests, not when configuring an org.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Not for configuring an org through a browser - that is sf-setup-automation.

    line 14
  2. Very specific:

    Not for component-level assertions, which belong in sf-lwc-jest-testing.

    line 15
  3. Distinctive:

    If a Jest test can fail on the bug, write the Jest test.

    line 29
  4. Very specific:

    No CSS selector for a Lightning internal ever appears in a spec.

    line 124

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Salesforce UI test automation

When to use

  • A regression escaped Apex tests and Jest because it lives in the rendered page.
  • A committed end-to-end suite is needed for a flow a user clicks through.
  • An existing UI suite is flaky, slow, or authenticates by pasting a password into a config file.
  • An Agentforce agent needs behavioural tests.
  • Not for configuring an org through a browser - that is sf-setup-automation.
  • Not for component-level assertions, which belong in sf-lwc-jest-testing.

Pick the tier that can actually catch the bug

A browser test is the most expensive assertion in the codebase. Work up the table, not down.

TierToolCatchesCost
Apex unitsf apex run testServer logic, sharing, triggers, governor limitsSeconds, gated by vf-check apex
LWC unit@salesforce/sfdx-lwc-jest 7.9.0Component rendering, wire adapters, DOM eventsSeconds, gated by vf-check jest
End-to-endUTAM + WebdriverIONavigation, Lightning shell, cross-component flows, permissions as a user sees themMinutes, needs an org
Agent behavioursf agent test runAgentforce topic and action selectionMinutes, needs an org
ExploratoryPlaywright MCP browser tools"What does this page even do" - never committed as a testInteractive

If a Jest test can fail on the bug, write the Jest test. Everything below assumes it cannot.

1. The UTAM stack, with versions

UTAM is Salesforce's UI Test Automation Model: page objects declared as .utam.json, compiled into JavaScript classes, driven by a runtime that resolves shadow DOM for you.

PackageLatestRole
@utam/core3.3.0Runtime API - utam.load, utam.getCurrentDocument
wdio-utam-service3.3.0WebdriverIO service that wires the runtime into a run
salesforce-pageobjects12.0.0"Page objects provided by Salesforce to test Lightning Experience using the UI Test Automation Model"
@salesforce/sfdx-lwc-jest7.9.0The tier below; present in the same project

salesforce-pageobjects is the part that makes this viable: the Lightning shell, app navigation and Flexipage internals are already modelled, so your own page objects only describe your components. There is no @utam/cli package - the compiler is invoked as utam -c utam.config.js.

2. Project shape

utam.config.js - what to compile and where the classes land:

module.exports = {
    pageObjectsFileMask: ['force-app/**/__utam__/**/*.utam.json'],
    pageObjectsOutputDir: 'pageObjects',
    alias: { 'utam-sfdx/': 'utam-js-recipes/', 'utam-*/': 'utam-preview/' },
    lint: { printToConsole: false },
};

wdio.conf.js - the service, and the Salesforce page objects injected into the runtime:

const { UtamWdioService } = require('wdio-utam-service');

exports.config = {
    specs: ['force-app/test/**/*.spec.js'],
    capabilities: [{ browserName: 'chrome', 'goog:chromeOptions': { args: ['--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu'] } }],
    services: [[UtamWdioService, { implicitTimeout: 0, injectionConfigs: ['salesforce-pageobjects/ui-global-components.config.json'] }]],
    framework: 'jasmine',
    waitforTimeout: 60 * 1000,
};

implicitTimeout: 0 is deliberate: UTAM waits on conditions, and an implicit wait on top of that turns one slow element into a multi-minute run. Full files and the npm scripts: references/utam-project.md.

3. Authenticate without a credential in the repo

Salesforce's own recipes get a login URL from the CLI and write it to .env - no password anywhere:

const getUrlCmd = 'sf org open -p /lightning -r --json';   // -r is --url-only
const { url } = JSON.parse(stdout).result;                  // frontdoor URL, single use

Two rules that follow from what that URL is. sf org open --url-only prints "This command will expose sensitive information that allows for subsequent activity using your current authenticated session", and the URL is minted single-use through /services/oauth2/singleaccess:

  • Regenerate it per run. A stored URL is both a credential and, after one navigation, useless.
  • .env holding it is gitignored, and the value never goes into a log line or a CI artefact.

In CI, run against a scratch org created for the job and deleted with it - see sf-scratch-orgs-sandboxes for the ephemeral-org script.

4. Page objects describe structure, specs describe behaviour

A page object is JSON: a root selector, elements typed by the page object that models them, and composed methods for multi-step interactions.

{
  "root": true,
  "selector": { "css": "body" },
  "elements": [
    { "name": "navigationBar", "type": "salesforce-pageobjects/global/pageObjects/appNav",
      "public": true, "selector": { "css": "one-appnav" } }
  ]
}

The spec then reads as navigation plus assertions, with waits expressed as conditions:

await browser.navigateTo(testEnvironment.sfdxLoginUrl);
const domDocument = utam.getCurrentDocument();
await domDocument.waitFor(async () => (await domDocument.getUrl()).includes('Hello'));
const appHomePage = await utam.load(HomePage);
const item = await (await (await appHomePage.getNavigationBar()).getAppNavBar()).getNavItem('Wire');
await item.clickAndWaitForUrl('lightning/n/Wire');

No CSS selector for a Lightning internal ever appears in a spec. When Salesforce restyles the shell, salesforce-pageobjects changes and your specs do not.

5. Agentforce agent tests

Agent behaviour is tested by definition, not by clicking: AiEvaluationDefinition metadata plus the CLI.

CommandDoes
sf agent generate test-specScaffold the test spec
sf agent test createCreate the test in the org from the spec
sf agent test runRun it
sf agent test resumeReattach to a run
sf agent test resultsFetch results
sf agent test run-evalRun an evaluation

Details of the metadata type live in sf-agentforce-development.

Anti-patterns

Anti-patternWhy it hurtsInstead
Raw CSS selectors for Lightning internals in a specBreak on every Salesforce release, and shadow DOM makes them unreachable anywaysalesforce-pageobjects page objects, own .utam.json for own components
browser.pause(5000)Passes on a fast org, fails in CI, hides the real conditionwaitFor on a URL, a text, or a loaded page object
Username and password in wdio.conf.js or a CI secret used by the browserA standing credential for a test runsf org open -p /lightning -r --json, regenerated per run
Reusing one login URL across specsSingle-use; the second spec lands on a login screenOne URL per run, or per spec if the run logs out
A UI test for logic a Jest test can reachMinutes of CI for a seconds-long assertionPush the assertion down a tier
UI suite in the blocking pre-merge gateAn org outage becomes a merge outageNightly or on-demand; vf-check local stays offline
Asserting on a toast onlyLightning shows a saved-looking page before validation completesAssert the persisted state, by query or by reload
--disable-web-security copied into a shared configMasks real CORS and CSP failuresKeep the recipes' --no-sandbox --disable-dev-shm-usage --disable-gpu and nothing more

Verification

npm run build                 # utam -c utam.config.js, then compile the page objects
npx wdio run wdio.conf.js --spec force-app/test/<one>.spec.js
sf apex run test --target-org <alias> --test-level RunLocalTests --code-coverage --result-format human
node "$CLAUDE_PLUGIN_ROOT/scripts/checks/vf-check.mjs" jest --changed
sf agent test run --target-org <alias>        # when the project has agents

A UI suite is healthy when the same spec passes twice in a row on a cold org, and its failure output names a condition that timed out rather than an element that vanished.

References

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.