<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Himanshu Agarwal</title>
    <description>The latest articles on DEV Community by Himanshu Agarwal (@himanshuai).</description>
    <link>https://dev.to/himanshuai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4011063%2F4b4436c8-ee93-4d2a-93ca-7eacce396438.png</url>
      <title>DEV Community: Himanshu Agarwal</title>
      <link>https://dev.to/himanshuai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/himanshuai"/>
    <language>en</language>
    <item>
      <title>Salesforce API Testing with Playwright + TypeScript (2026 Edition)</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Fri, 07 Aug 2026 12:16:08 +0000</pubDate>
      <link>https://dev.to/himanshuai/salesforce-api-testing-with-playwright-typescript-2026-edition-478f</link>
      <guid>https://dev.to/himanshuai/salesforce-api-testing-with-playwright-typescript-2026-edition-478f</guid>
      <description>&lt;h2&gt;
  
  
  REST, OAuth, Bulk API &amp;amp; Integration Testing
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Written by Himanshu Agarwal&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Most teams treat Salesforce API testing as an afterthought. They ship a handful of happy-path &lt;code&gt;POST /sobjects/Account&lt;/code&gt; checks, wire them into a nightly job, and declare the integration "covered." Then a Winter release lands, a Named Credential rotates, a Bulk job silently drops ten thousand records because of a malformed CSV column, and the on-call engineer spends a weekend reconstructing what the pipeline actually did.&lt;/p&gt;

&lt;p&gt;If you have spent five to fifteen years building distributed systems, you already know why that happens. Salesforce is not a REST API you test. It is a multi-tenant platform with governor limits, asynchronous jobs, a proprietary query language, an authentication surface that spans six OAuth flows, and a data model that other enterprise systems — SAP, Oracle, MuleSoft, Kafka, payment gateways — write into constantly. Testing it well means testing the seams between those systems, not just the endpoints.&lt;/p&gt;

&lt;p&gt;This article is about building a production-grade Salesforce API testing framework with Playwright and TypeScript. Playwright's &lt;code&gt;APIRequestContext&lt;/code&gt; has quietly become one of the best HTTP clients available for test engineering: it is fast, it lives in the same runtime as your UI tests, it has first-class fixtures, tracing, and reporting, and it does not force you into the ceremony of a separate contract tool just to fire an authenticated request. We will use it as the request layer and build everything an enterprise needs on top: token lifecycle management, retry and rate-limit handling, correlation IDs, schema validation, Bulk API orchestration, security assertions, and CI/CD wiring.&lt;/p&gt;

&lt;p&gt;This is a 2026 edition, and the specifics matter. As of the current Salesforce release train, Winter '26 shipped as API version 65.0 and Spring '26 as version 66.0, with the platform continuing its three-releases-per-year cadence and a minimum three-year version support window. Playwright's &lt;code&gt;APIRequestContext&lt;/code&gt; now supports options like &lt;code&gt;failOnStatusCode&lt;/code&gt; and improved tracing that change how you structure a framework. OWASP's API Security Top 10 remains on its 2023 edition, which reorganized the risk landscape around authorization and business-flow abuse. We will build against these realities, not against a 2021 mental model.&lt;/p&gt;

&lt;p&gt;Everything here is implementation-oriented. You will see folder structures, fixtures, retry logic, JWT signing, Bulk ingest orchestration, and CI pipelines you can adapt directly. The goal is a framework you would actually run against a production org.&lt;/p&gt;

&lt;h2&gt;
  
  
  HimanshuAI August Sale — FLAT 95% OFF
&lt;/h2&gt;

&lt;p&gt;The HimanshuAI August Sale is now live.&lt;/p&gt;

&lt;p&gt;For a limited time, get FLAT 95% OFF on my complete collection of premium AI Engineering digital playbooks.&lt;/p&gt;

&lt;p&gt;New Bundles:&lt;/p&gt;

&lt;p&gt;• GenAI Engineering Vault — 16 Books&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• THE BUNDLE — LLM &amp;amp; Generative AI Testing Pro&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Coding Agents Mastery — Volume 1&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Ollama &amp;amp; Local LLMs — Complete 4 Book Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AWS Cloud Tester Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Salesforce Automation Testing Mastery Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Playwright + TypeScript Mastery Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coupon Code&lt;/p&gt;

&lt;p&gt;AI95&lt;/p&gt;

&lt;p&gt;Flat 95% OFF&lt;/p&gt;

&lt;p&gt;Explore&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Enterprise Salesforce API Testing Is Different
&lt;/h2&gt;

&lt;p&gt;A generic REST API returns predictable status codes and behaves the same for every caller. Salesforce does not, and the differences are exactly where enterprise test suites break.&lt;/p&gt;

&lt;p&gt;The first difference is governor limits. Salesforce enforces per-org, rolling 24-hour API request allocations tied to edition and license count, plus concurrent long-running request limits, plus per-transaction limits inside Apex. A test suite that hammers the org in parallel does not just risk flakiness — it can exhaust the org's daily allocation and take down real integrations sharing that org. Your framework has to be a good tenant.&lt;/p&gt;

&lt;p&gt;The second is that Salesforce does not fail the way you expect. There is no clean, universal &lt;code&gt;429 Too Many Requests&lt;/code&gt;. When you exceed the daily API request limit, the classic response is HTTP 403 with an error code of &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt; in the body. Concurrent request ceilings surface differently again, and only some newer platform surfaces emit a true 429 with a &lt;code&gt;Retry-After&lt;/code&gt; header. If your retry logic keys purely on the 429 status, it will miss the most common Salesforce throttling case entirely. Robust handling parses the Salesforce error code, not just the HTTP status.&lt;/p&gt;

&lt;p&gt;The third is asynchronicity. Bulk API 2.0, the Metadata API, Platform Events, and Change Data Capture are all eventually consistent. You submit a job, you get an accepted response, and the actual work happens later. A test that asserts immediately after submission is testing the queue, not the outcome. Real coverage means polling job state, reconciling successful and failed record sets, and validating data integrity after the fact.&lt;/p&gt;

&lt;p&gt;The fourth is the authentication surface. A typical service has one auth mechanism. A serious Salesforce integration touches several: Authorization Code with PKCE for user-facing apps, JWT Bearer for server-to-server automation, Client Credentials for headless services, refresh tokens for long-lived sessions, and Named Credentials abstracting all of it for Apex callouts. Each has a different token lifecycle, and each fails differently.&lt;/p&gt;

&lt;p&gt;The fifth is the ecosystem. Nobody runs Salesforce in isolation. Leads flow in from marketing platforms, orders sync to SAP, entitlements arrive from a billing system, events stream through Kafka or MuleSoft. The bugs that hurt most in production are not inside Salesforce — they are in the translation layer between Salesforce and everything else. Enterprise testing has to assert on those contracts.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Modern Salesforce API Ecosystem
&lt;/h2&gt;

&lt;p&gt;Before writing a single test, you need a working mental map of which API does what, because choosing the wrong one is the most common architectural mistake in Salesforce test design.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;REST API&lt;/strong&gt; is the workhorse for synchronous, record-level CRUD. It exposes &lt;code&gt;/services/data/vXX.0/sobjects/{Object}&lt;/code&gt;, SOQL queries via &lt;code&gt;/query&lt;/code&gt;, and search via &lt;code&gt;/search&lt;/code&gt;. It is what you reach for by default, and what most of your functional tests will use.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;SOAP API&lt;/strong&gt; predates REST and is still heavily used by legacy middleware and by tools that consume the Enterprise or Partner WSDL. If you are testing an integration built on an older MuleSoft or Boomi connector, you may be asserting against SOAP payloads whether you like it or not. Playwright can send raw XML bodies, so it handles SOAP fine, but expect verbose envelopes.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Composite API&lt;/strong&gt; is the efficiency play. &lt;code&gt;/composite&lt;/code&gt; batches up to 25 subrequests into one round trip and lets later subrequests reference earlier ones by &lt;code&gt;referenceId&lt;/code&gt; — invaluable for creating a parent and child in a single call. &lt;code&gt;/composite/tree/{Object}&lt;/code&gt; inserts nested record trees up to 200 records. The sObject Collections endpoints (&lt;code&gt;/composite/sobjects&lt;/code&gt;) operate on up to 200 records of the same type in one request. &lt;code&gt;/composite/graph&lt;/code&gt; handles more complex dependency graphs with transactional boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bulk API 2.0&lt;/strong&gt; is for volume. It is CSV-based and fully asynchronous: you create an ingest job, upload data, mark it complete, then poll for results. It is the correct tool for anything above a few thousand records, and it has entirely different failure semantics from REST.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Streaming API&lt;/strong&gt; covers event-driven surfaces: PushTopics, generic events, Platform Events, and Change Data Capture, delivered over CometD/long-polling. Testing it means subscribing, triggering a change, and asserting the event arrives — a genuinely different pattern from request/response.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Tooling API&lt;/strong&gt; is for developer and metadata-adjacent operations: Apex execution, code coverage, symbol tables, and, as of recent releases, unified test discovery and execution endpoints. Test infrastructure tooling often leans on it.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Metadata API&lt;/strong&gt; deploys and retrieves org configuration. You rarely assert business logic through it, but deployment validation tests and environment-drift checks live here.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;GraphQL API&lt;/strong&gt;, available at &lt;code&gt;/services/data/vXX.0/graphql&lt;/code&gt;, lets clients request exactly the fields they need across related objects in one query. It is increasingly used by Lightning components and mobile clients, and it deserves its own contract tests because the shape of the response is client-defined.&lt;/p&gt;
&lt;h3&gt;
  
  
  Choosing the Correct API
&lt;/h3&gt;

&lt;p&gt;The decision rules are simple once stated plainly. Use REST for single-record and small-batch synchronous work. Use Composite when you would otherwise make several dependent REST calls and want them atomic or want to save round trips. Use Bulk 2.0 once record counts cross into the thousands or when you are validating a data migration. Use Streaming when the behavior under test is event delivery. Use GraphQL when the client controls the response shape and you need to guard against over- or under-fetching. Reach for SOAP only when the integration you are testing already speaks it. The wrong choice does not just make tests slow — it makes them lie, because a Bulk job that "succeeded" at the HTTP layer can still have failed every row.&lt;/p&gt;
&lt;h2&gt;
  
  
  Enterprise Authentication
&lt;/h2&gt;

&lt;p&gt;Authentication is where most Salesforce test frameworks either stay simple and fragile, or become robust and reusable. The difference is treating token acquisition as a first-class, cached, observable subsystem rather than a copy-pasted helper.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Flows You Actually Test Against
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Authorization Code (with PKCE)&lt;/strong&gt; is the user-facing flow. Your automated suite rarely drives the full browser redirect for API tests, but you do test the token exchange and refresh behavior of apps that use it. RFC 6749 defines the flow; PKCE (RFC 7636) is now expected even for confidential clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JWT Bearer&lt;/strong&gt; is the backbone of headless CI automation against Salesforce. You register a Connected App with a digital certificate, sign a JWT with the matching private key, and exchange it for an access token. There is no user interaction and no refresh token — you simply mint a new assertion when the token expires. This is almost always the right flow for a test framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client Credentials&lt;/strong&gt; is Salesforce's server-to-server flow for integrations with no user context. You enable it on the Connected App and designate a run-as user. It returns an access token with that user's permissions and, like JWT, issues no refresh token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refresh Token&lt;/strong&gt; flow keeps long-lived sessions alive for apps that did an initial interactive login. In tests you validate that a refresh yields a new access token and that the old one is invalidated per policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Named Credentials&lt;/strong&gt; are a Salesforce-side abstraction: they store the endpoint and auth for outbound callouts made from Apex or Flow, so developers never handle raw tokens. You do not authenticate &lt;em&gt;through&lt;/em&gt; them from Playwright, but when you test an Apex-driven integration, the Named Credential is the thing that can be misconfigured, so your negative tests should account for it.&lt;/p&gt;
&lt;h3&gt;
  
  
  JWT Bearer in Practice
&lt;/h3&gt;

&lt;p&gt;The JWT Bearer flow is worth showing end to end because it is the one your framework will lean on. The assertion is a signed JWT whose claims identify the Connected App (&lt;code&gt;iss&lt;/code&gt;), the user to impersonate (&lt;code&gt;sub&lt;/code&gt;), the login audience (&lt;code&gt;aud&lt;/code&gt;), and an expiry (&lt;code&gt;exp&lt;/code&gt;) a few minutes out. It is signed RS256 with the private key that matches the certificate uploaded to the Connected App.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/auth/jwt-bearer.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSign&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;JwtBearerConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Connected App consumer key&lt;/span&gt;
  &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// user to impersonate (sub)&lt;/span&gt;
  &lt;span class="nl"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// https://login.salesforce.com or My Domain / test.salesforce.com&lt;/span&gt;
  &lt;span class="nl"&gt;privateKeyPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// PEM private key matching the app certificate&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/=/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildSignedAssertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JwtBearerConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RS256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;typ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JWT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;iss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;aud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 3 minute window&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signingInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privateKeyPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RSA-SHA256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signingInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;signingInput&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token exchange itself is a single form POST. Notice that we never pass a client secret in JWT Bearer — the signature &lt;em&gt;is&lt;/em&gt; the proof.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/auth/token-service.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;playwrightRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildSignedAssertion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./jwt-bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expiresInMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;JWT_GRANT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urn:ietf:params:oauth:grant-type:jwt-bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Refresh a little before the real expiry to avoid mid-test 401s.&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;safetyWindowMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isExpiring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mintSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;isExpiring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issuedAt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresInMs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;safetyWindowMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;mintSession&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildSignedAssertion&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;privateKeyPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_JWT_KEY_PATH&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;playwrightRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth2/token`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JWT_GRANT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`JWT token exchange failed &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Salesforce access tokens do not carry a numeric TTL in this response;&lt;/span&gt;
    &lt;span class="c1"&gt;// treat them as session-lifetime and cap our own cache conservatively.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;expiresInMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;invalidate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token Lifecycle, Expiration, and Secret Management
&lt;/h3&gt;

&lt;p&gt;Two failure modes dominate real suites. The first is the mid-run 401: a token acquired at the start of a long parallel run expires before the last test uses it. The safety-window cache above handles this, and the request layer we build next will additionally re-mint on a 401 and retry once. The second is leaked secrets. Never commit private keys, consumer secrets, or usernames. In CI they belong in the runner's secret store; locally they belong in an untracked &lt;code&gt;.env&lt;/code&gt; or, better, pulled at runtime from a vault.&lt;/p&gt;

&lt;p&gt;A vault integration keeps the same interface but sources secrets externally, which means rotation never requires a code change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/auth/secret-provider.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SecretProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Vault-backed provider (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault&lt;/span&gt;
&lt;span class="c1"&gt;// all fit this shape). Fetch once, cache in-process, never log the value.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VaultSecretProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;SecretProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The discipline that matters: secrets are read once per process, cached in memory, and never written to logs, reports, or trace files. Playwright traces capture request bodies, so scrub the &lt;code&gt;Authorization&lt;/code&gt; header and any token in your logging layer before it reaches disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright API Testing Architecture
&lt;/h2&gt;

&lt;p&gt;A framework is not a folder of test files. It is a set of layers with clear responsibilities, so that a business-facing test reads like business language and the plumbing lives underneath it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Folder Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salesforce-api-tests/
  src/
    auth/
      jwt-bearer.ts
      token-service.ts
      secret-provider.ts
    core/
      sf-client.ts          # request layer over APIRequestContext
      retry.ts              # retry + backoff policy
      correlation.ts        # correlation id generation
      logger.ts             # structured, secret-scrubbed logging
      errors.ts             # typed Salesforce error parsing
    domain/
      accounts.ts           # Account-specific request helpers
      leads.ts
      opportunities.ts
      bulk.ts               # Bulk API 2.0 orchestration
    schemas/
      account.schema.json
      lead.schema.json
    config/
      env.ts                # typed environment loader
  tests/
    rest/
    composite/
    bulk/
    contract/
    security/
    performance/
  fixtures/
    sf-fixtures.ts
  playwright.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The separation is deliberate. &lt;code&gt;core&lt;/code&gt; knows nothing about Accounts or Leads. &lt;code&gt;domain&lt;/code&gt; knows nothing about retry mechanics. Tests know nothing about tokens. When Salesforce bumps an API version or a limit changes, you touch one layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration and Environment Management
&lt;/h3&gt;

&lt;p&gt;Environment drift — a test that passes in QA and fails in staging because a URL, limit, or feature flag differs — is one of the top causes of "flaky" Salesforce suites. Kill it by making configuration typed and explicit, failing fast when something is missing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/config/env.ts&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Missing required env var: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_API_VERSION&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v65.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;jwtKeyPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_JWT_KEY_PATH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_MAX_RETRIES&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;requestTimeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_TIMEOUT_MS&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin the API version explicitly rather than always chasing the newest. Salesforce guarantees a multi-year support window per version, and pinning means a release upgrade cannot silently change response shapes underneath your assertions. You upgrade the version deliberately, run the suite, and only then move forward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixtures and Dependency Injection
&lt;/h3&gt;

&lt;p&gt;Playwright fixtures are the cleanest dependency-injection mechanism available to a test engineer. We build a single authenticated Salesforce client fixture that every test can request by name, and Playwright handles construction and teardown.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// fixtures/sf-fixtures.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;TokenService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/auth/token-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../src/core/sf-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SfFixtures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Worker-scoped token service so we mint one session per worker, not per test.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TokenService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SfFixtures&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;playwright&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tokenService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokenService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a test simply asks for &lt;code&gt;sf&lt;/code&gt; and receives a fully authenticated, retry-aware, logging client. No test ever touches a token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise API Framework Design
&lt;/h2&gt;

&lt;p&gt;This is the heart of the framework: a request layer that turns Playwright's raw &lt;code&gt;APIRequestContext&lt;/code&gt; into something a large team can rely on. It owns headers, retries, rate-limit awareness, correlation IDs, and structured logging.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Request Layer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/core/sf-client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Playwright&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TokenService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../auth/token-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withRetry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;newCorrelationId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./correlation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseSalesforceError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./errors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../config/env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SfRequestOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesforceClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TokenService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Playwright&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TokenService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestTimeoutMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// failOnStatusCode stays false: we want to inspect and classify errors,&lt;/span&gt;
      &lt;span class="c1"&gt;// not throw blindly on the first non-2xx.&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;baseHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Correlation-Id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SfRequestOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;APIResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;newCorrelationId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sf.request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// Re-mint on auth failure, then let retry re-run once with a fresh token.&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RetryableError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token_expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;parseSalesforceError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RetryableError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RetryableError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`retryable:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Typed Error Parsing
&lt;/h3&gt;

&lt;p&gt;The single most valuable piece of Salesforce-specific logic in the framework is correctly classifying errors. Salesforce encodes the real reason in the response body, not just the status line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/core/errors.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RETRYABLE_SF_CODES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REQUEST_LIMIT_EXCEEDED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// daily API allocation (HTTP 403)&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SERVER_UNAVAILABLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UNABLE_TO_LOCK_ROW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// row-lock contention, transient&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ParsedSfError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;retryAfterMs&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseSalesforceError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ParsedSfError&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;retry-after&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`HTTP_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;statusText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Non-JSON body (e.g. HTML error page); keep HTTP-derived code.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;retryable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;RETRYABLE_SF_CODES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;retryAfterMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;retryAfter&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the detail beginners miss and staff engineers insist on: a &lt;code&gt;403 REQUEST_LIMIT_EXCEEDED&lt;/code&gt; is &lt;em&gt;retryable with backoff&lt;/em&gt;, while a &lt;code&gt;403 INSUFFICIENT_ACCESS&lt;/code&gt; is a hard permission failure that must never be retried. Keying only on the HTTP status conflates them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retry, Backoff, and Rate Limiting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/core/retry.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../config/env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RetryableError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./sf-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RetryCtx&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RetryCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;lastErr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;lastErr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;RetryableError&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// Exponential backoff with full jitter, capped.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sf.retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;delayMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;lastErr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full jitter matters at enterprise scale. If forty parallel workers all hit &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt; and back off on identical fixed intervals, they retry in lockstep and re-trigger the limit. Randomized backoff spreads the load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Correlation IDs, Tracing, and Observability
&lt;/h3&gt;

&lt;p&gt;Every request carries an &lt;code&gt;X-Correlation-Id&lt;/code&gt;. When a test fails in CI at 3 a.m., that ID is what lets you grep the structured logs, find the exact request, its retries, and its final status, and — if the org's event monitoring is enabled — correlate it with the server-side API event log. Combined with Playwright's built-in trace (&lt;code&gt;trace: 'retain-on-failure'&lt;/code&gt; in the config), you get client-side timing, request/response bodies, and the full retry timeline for any failure, without instrumenting each test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/core/correlation.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomUUID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newCorrelationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`pw-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observability is not an add-on here; it is the difference between a suite you can operate and one you merely run.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST API Automation
&lt;/h2&gt;

&lt;p&gt;With the framework in place, functional REST tests become short and readable. Here is a realistic Lead creation scenario with validation, business-rule assertions, and cleanup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/rest/lead-crud.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../fixtures/sf-fixtures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lead lifecycle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creates a Lead and enforces required fields&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sobjects/Lead&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Agarwal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Company&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Northwind Traders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lead.northwind@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;LeadSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Open - Not Contacted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^00Q/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Lead key prefix&lt;/span&gt;
    &lt;span class="nx"&gt;leadId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reads the Lead back with expected field values&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`sobjects/Lead/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Company&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Northwind Traders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IsConverted&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rejects creation without Company (business rule)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sobjects/Lead&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NoCompany&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REQUIRED_FIELD_MISSING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`sobjects/Lead/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pagination, Filtering, and Sorting
&lt;/h3&gt;

&lt;p&gt;SOQL queries return a first page plus a &lt;code&gt;nextRecordsUrl&lt;/code&gt; when results exceed the batch size. A correct test framework follows the cursor rather than assuming one page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/domain/query.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../core/sf-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;queryAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;soql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;soql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextRecordsUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// nextRecordsUrl is an absolute path already scoped to the API version.&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextRecordsUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;services&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;data&lt;/span&gt;&lt;span class="se"&gt;\/[^/]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filtering and sorting are expressed in SOQL (&lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt;, &lt;code&gt;LIMIT&lt;/code&gt;), which means your tests are asserting the platform's query semantics, not a REST query-string convention. Guard against SOQL injection in any test helper that interpolates user-like input — bind or escape it, because the same injection risk that hurts production hurts test fixtures that seed data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composite API Testing
&lt;/h2&gt;

&lt;p&gt;The Composite API's superpower is dependent creation in one transaction. This test creates an Account and a contact that references it, using &lt;code&gt;referenceId&lt;/code&gt;, and asserts atomicity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/composite/account-contact.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../fixtures/sf-fixtures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creates Account and related Contact atomically&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;composite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;allOrNone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;compositeRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/v65.0/sobjects/Account`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newAccount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contoso Ltd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/v65.0/sobjects/Contact`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newContact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sharma&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@{newAccount.id}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sharma.contoso@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compositeResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;httpStatusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;httpStatusCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// With allOrNone true, a failure in either subrequest rolls back both.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referenceId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newAccount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contactId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referenceId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newContact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contactId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The negative test — send a Contact with an invalid field and assert that &lt;code&gt;allOrNone&lt;/code&gt; rolls back the Account too — is the one that actually protects you, because partial-commit bugs are what corrupt production data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bulk API Testing
&lt;/h2&gt;

&lt;p&gt;Bulk API 2.0 is where synchronous testing habits break. The flow has four distinct phases, and every one of them can fail independently: create the job, upload CSV data, mark the upload complete, then poll until the job reaches a terminal state and reconcile the per-row results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/domain/bulk.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../core/sf-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;BulkJob&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createIngestJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upsert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;externalIdField&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BulkJob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jobs/ingest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CSV&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lineEnding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;externalIdField&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;externalIdFieldName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;externalIdField&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The batches endpoint expects text/csv, not JSON.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/batches`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`CSV upload failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;closeJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PATCH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UploadComplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pollUntilComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BulkJob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JobComplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Aborted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bulk job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; did not complete within &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Large Dataset Validation and Data Integrity
&lt;/h3&gt;

&lt;p&gt;A job state of &lt;code&gt;JobComplete&lt;/code&gt; does not mean every row succeeded. Bulk 2.0 can complete with a mix of successful and failed records, so integrity validation means fetching and reconciling all three result sets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/bulk/account-import.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../fixtures/sf-fixtures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createIngestJob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uploadCsv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;closeJob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pollUntilComplete&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../src/domain/bulk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;imports 5,000 Accounts and reconciles results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name,Industry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bulk Account &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,Manufacturing`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createIngestJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;uploadCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;closeJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pollUntilComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JobComplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failedRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/failedResults`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failedCsv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;failedRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failedRowCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;failedCsv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// minus header&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failedRowCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;successRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/successfulResults`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;successCsv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;successRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;successRowCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;successCsv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;successRowCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Failure Handling and Retry Strategy for Bulk
&lt;/h3&gt;

&lt;p&gt;Bulk failures are almost never "retry the whole job." They are "identify the failed rows, understand why, and re-submit only those." The &lt;code&gt;failedResults&lt;/code&gt; CSV includes an &lt;code&gt;sf__Error&lt;/code&gt; column with the per-row error. The correct retry strategy parses that column, filters transient errors (row locks, storage limits) from permanent ones (validation failures, missing required fields), builds a new CSV of only the transient failures, and submits a fresh job. Blindly re-running the whole file risks creating duplicates for the rows that already succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contract Testing
&lt;/h2&gt;

&lt;p&gt;Functional tests prove behavior against a live org. Contract tests prove the &lt;em&gt;shape&lt;/em&gt; of the exchange, and they catch a different, sneakier class of bug: a downstream consumer silently breaking because a field type changed or a nullable field started returning null.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema Validation with JSON Schema
&lt;/h3&gt;

&lt;p&gt;Every important response should be validated against a JSON Schema. Use a schema validator such as Ajv so a drift in the response contract fails a test loudly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/core/schema.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Ajv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;JSONSchemaType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ajv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;addFormats&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ajv-formats&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ajv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ajv&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;allErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nf"&gt;addFormats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ajv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;asserts&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ajv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Schema validation failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/contract/account-schema.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../fixtures/sf-fixtures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;accountSchema&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../src/schemas/account.schema.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assertSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../src/core/schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account response conforms to contract&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT Id, Name, Industry, AnnualRevenue FROM Account LIMIT 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nf"&gt;assertSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  OpenAPI and Consumer-Driven Contracts
&lt;/h3&gt;

&lt;p&gt;For custom Apex REST endpoints and integration middleware, an OpenAPI specification becomes the source of truth. Generate request/response validation from the spec so any endpoint that drifts from its documented contract fails CI. When Salesforce is the &lt;em&gt;provider&lt;/em&gt; and an external service is the &lt;em&gt;consumer&lt;/em&gt;, consumer-driven contract testing with a tool like Pact lets the consumer publish its expectations and the provider verify them independently. Pact's value in a Salesforce context is decoupling: the MuleSoft team can evolve their consumer, publish an updated contract to a broker, and your provider verification catches an incompatibility before either side deploys. It is not a replacement for integration tests — it is insurance against the two teams disagreeing about the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Testing
&lt;/h2&gt;

&lt;p&gt;Security assertions belong in the same suite as functional ones, mapped to the OWASP API Security Top 10 (2023 edition), whose most critical categories are all about authorization and business-flow abuse rather than injection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broken Object Level Authorization (API1)&lt;/strong&gt; is the number-one API risk. In Salesforce terms, it maps to record-level sharing. Test it by authenticating as a low-privilege user and attempting to read a record they should not see; a correctly configured org returns a 404 or an empty result, never the record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/security/bola.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../fixtures/sf-fixtures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low-privilege user cannot read a restricted Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// sfLowPriv is a second client fixture authenticated as a restricted user.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESTRICTED_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Broken Authentication (API2)&lt;/strong&gt; tests confirm that expired, malformed, and tampered tokens are all rejected with 401, and that a token minted for one org cannot be replayed against another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Broken Object Property Level Authorization (API3)&lt;/strong&gt; merges the old Excessive Data Exposure and Mass Assignment risks. On the exposure side, assert that field-level security actually hides sensitive fields — a query for a restricted field should not return it. On the mass-assignment side, attempt to set a field the user should not control (for example, an &lt;code&gt;OwnerId&lt;/code&gt; or an audit field) and assert the platform ignores or rejects it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unrestricted Resource Consumption (API4)&lt;/strong&gt; is where rate limiting and payload-size limits live. Confirm the org enforces limits and that your client handles the enforcement gracefully.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Injection&lt;/strong&gt; in the Salesforce context is primarily SOQL injection through poorly built query strings in custom endpoints. Test any Apex REST endpoint that accepts input by sending crafted values (&lt;code&gt;' OR Name != '&lt;/code&gt;) and asserting they are treated as literals, not query fragments.&lt;/p&gt;

&lt;p&gt;The pattern that matters: security tests are negative tests that must &lt;em&gt;fail closed&lt;/em&gt;. A passing security test is one where the malicious request was correctly denied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Testing
&lt;/h2&gt;

&lt;p&gt;Playwright is not a load-testing tool in the way k6 or Gatling are, but its &lt;code&gt;APIRequestContext&lt;/code&gt; is excellent for latency assertions, concurrency behavior, and catching response-time regressions inside your functional suite. For true sustained load, generate traffic with a purpose-built tool; for guardrail checks that run every build, Playwright is ideal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/performance/latency.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../fixtures/sf-fixtures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;single Account read stays under latency budget&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SAMPLE_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// p-latency budget for this endpoint&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handles 20 concurrent reads without errors&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SAMPLE_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep two things honest here. First, latency budgets should be percentile-based over many runs, not a single-shot assertion — a single slow call proves nothing. Second, respect the org. Concurrency tests against a shared org can trip concurrent-request limits and affect other users; run heavy concurrency only against dedicated performance sandboxes. Response-time trends belong in a dashboard, tracked over time, so a gradual regression is visible before it becomes an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  HimanshuAI August Sale — FLAT 95% OFF
&lt;/h2&gt;

&lt;p&gt;The HimanshuAI August Sale is now live.&lt;/p&gt;

&lt;p&gt;For a limited time, get FLAT 95% OFF on my complete collection of premium AI Engineering digital playbooks.&lt;/p&gt;

&lt;p&gt;New Bundles:&lt;/p&gt;

&lt;p&gt;• GenAI Engineering Vault — 16 Books&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• THE BUNDLE — LLM &amp;amp; Generative AI Testing Pro&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Coding Agents Mastery — Volume 1&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Ollama &amp;amp; Local LLMs — Complete 4 Book Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AWS Cloud Tester Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Salesforce Automation Testing Mastery Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Playwright + TypeScript Mastery Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coupon Code&lt;/p&gt;

&lt;p&gt;AI95&lt;/p&gt;

&lt;p&gt;Flat 95% OFF&lt;/p&gt;

&lt;p&gt;Explore&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Integration Testing
&lt;/h2&gt;

&lt;p&gt;The highest-value tests in an enterprise Salesforce landscape are the ones that cross system boundaries. Salesforce is rarely the system of record for everything; it is one node in a graph that includes SAP for finance and orders, Oracle for legacy master data, payment gateways for billing, and middleware — MuleSoft, Boomi, Azure Logic Apps — moving data between them, often with Kafka as the event backbone.&lt;/p&gt;

&lt;p&gt;The architectural principle for testing these flows is to assert at the seams. A Lead-to-Opportunity flow that starts in a marketing platform, lands in Salesforce, and triggers an order in SAP has three seams, and each is a place data can be lost or mangled. You test each seam independently and then end to end.&lt;/p&gt;

&lt;p&gt;Consider a Salesforce-to-SAP order sync mediated by MuleSoft. The realistic test does three things: it creates the Order in Salesforce through the REST API, it waits for the middleware to process (which is asynchronous, so you poll rather than assume), and it verifies the record materialized correctly on the SAP side through SAP's own API. Playwright handles all three because it is just an HTTP client with good ergonomics — the SAP call is another &lt;code&gt;APIRequestContext&lt;/code&gt; with different auth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/integration/order-sync.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../fixtures/sf-fixtures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pollFor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../src/core/poll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order created in Salesforce syncs to SAP via MuleSoft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sapClient&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Create the Order in Salesforce.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sobjects/Order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SAMPLE_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;EffectiveDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-08-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sfOrderId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Poll SAP for the synced order (middleware is asynchronous).&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sapOrder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pollFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sapClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrderBySalesforceRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sfOrderId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;intervalMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Assert data integrity across the seam.&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sapOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;externalRef&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sfOrderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sapOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For event-driven integrations — Salesforce Platform Events or Change Data Capture flowing into Kafka — the test subscribes to the downstream topic, triggers the change in Salesforce, and asserts the event arrives with the right payload within a timeout. The same pattern applies to Salesforce plus Azure (via Logic Apps or Service Bus), Salesforce plus AWS (via EventBridge or an API Gateway endpoint), and Salesforce plus an AI platform (where a record change triggers an enrichment call and you assert the enriched fields come back). The constant across all of them is: create on one side, poll on the other, reconcile the payload. Never assert synchronously across an asynchronous seam.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;p&gt;A framework that only runs on a laptop is a prototype. Production value comes from running on every pull request and every deploy, in parallel, with secrets handled safely.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/sf-api-tests.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Salesforce API Tests&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;   &lt;span class="c1"&gt;# nightly regression&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;shard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="c1"&gt;# API tests need no browser download; keep the job lean.&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Playwright API tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright test --shard=${{ matrix.shard }}/4&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_LOGIN_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_JWT_KEY_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./sf_key.pem&lt;/span&gt;
          &lt;span class="na"&gt;SF_JWT_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_JWT_KEY }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report-${{ matrix.shard }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The private key is injected as a secret and written to a file in a pre-step; it is never committed. Sharding runs the suite across four parallel jobs, which cuts wall-clock time and, importantly, spreads API load rather than concentrating it in one worker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure DevOps and Jenkins
&lt;/h3&gt;

&lt;p&gt;The same shape ports directly. In Azure DevOps, secrets come from a variable group backed by Azure Key Vault, and parallelism uses a matrix strategy in the pipeline YAML. In Jenkins, credentials come from the Credentials plugin (or a Vault plugin), and parallelism uses either a declarative &lt;code&gt;matrix&lt;/code&gt; block or parallel stages. Across all three, the non-negotiables are identical: secrets from a managed store, environment-specific configuration injected at runtime, parallel execution to control both time and load, and artifacts (reports, traces, logs) published on every run — especially failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel Execution and the Org as a Shared Resource
&lt;/h3&gt;

&lt;p&gt;The subtlety that separates senior CI design from naive CI design is remembering the org is shared. Uncontrolled parallelism can exhaust the daily API allocation or trip concurrency limits, turning a green suite red for reasons that have nothing to do with the code under test. Cap &lt;code&gt;workers&lt;/code&gt; in the Playwright config to a number the target org can absorb, use a dedicated CI integration user so its API consumption is attributable, and stagger heavy suites (Bulk, performance) away from peak sandbox usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reporting
&lt;/h2&gt;

&lt;p&gt;Reporting is how a failing test becomes an actionable ticket. Playwright's built-in reporters cover most needs, and enterprise suites usually layer a richer view on top.&lt;/p&gt;

&lt;p&gt;The HTML reporter gives an interactive, per-test view with embedded traces — the first place an engineer looks. The JUnit reporter emits XML that every CI system understands, feeding native test dashboards and gating merges. Allure adds historical trends, severity tagging, and step-level detail that leadership and QA managers actually read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.ts (reporter excerpt)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;junit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results/junit.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;allure-playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;retain-on-failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond the standard reporters, retain artifacts that make debugging fast: structured API logs keyed by correlation ID, the request/response bodies captured in traces (with secrets scrubbed), and any screenshots from the rare UI-plus-API hybrid tests. For pure API suites there are no screenshots, but the correlation-ID logs and traces together reconstruct exactly what happened without re-running anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;p&gt;The practices that hold up across large Salesforce test suites are consistent regardless of team or industry.&lt;/p&gt;

&lt;p&gt;Treat authentication as infrastructure, not per-test code. One cached, observable token service used by every test eliminates an entire category of flakiness.&lt;/p&gt;

&lt;p&gt;Pin the API version and upgrade it deliberately. Chasing the newest version on every release is how response-shape changes ambush you.&lt;/p&gt;

&lt;p&gt;Classify errors by Salesforce error code, not just HTTP status. &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt; and &lt;code&gt;INSUFFICIENT_ACCESS&lt;/code&gt; are both 403 and demand opposite responses.&lt;/p&gt;

&lt;p&gt;Back off with jitter. Fixed-interval retries at scale re-create the exact limit condition you are trying to escape.&lt;/p&gt;

&lt;p&gt;Poll, never sleep-and-assume, for anything asynchronous. Bulk jobs, Platform Events, and middleware syncs are eventually consistent.&lt;/p&gt;

&lt;p&gt;Make tests self-cleaning. Every test that creates data deletes it, ideally in an &lt;code&gt;afterEach&lt;/code&gt; or &lt;code&gt;afterAll&lt;/code&gt;, so the org does not accumulate junk that skews later runs.&lt;/p&gt;

&lt;p&gt;Carry a correlation ID on every request. When something fails in CI, the ID is the thread you pull to find the truth.&lt;/p&gt;

&lt;p&gt;Isolate test data. Use unique, namespaced values (a run ID in record names) so parallel workers never collide and assertions never match another test's data.&lt;/p&gt;

&lt;p&gt;Respect the org as a shared tenant. Cap concurrency, use a dedicated integration user, and keep heavy suites off peak sandbox hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;The failures repeat across organizations. Hardcoding tokens or instance URLs guarantees a broken suite the moment anything rotates. Asserting synchronously after an async submission tests the queue, not the result, and produces intermittent green that means nothing. Retrying on raw HTTP status alone retries permission failures forever and never retries the throttling that actually needs it. Sharing mutable test data between tests creates order-dependent suites that pass locally and fail in parallel CI. Ignoring &lt;code&gt;failedResults&lt;/code&gt; on a Bulk job and trusting &lt;code&gt;JobComplete&lt;/code&gt; lets silent data loss ship. Logging full request bodies with tokens intact leaks credentials into artifacts. And building one giant &lt;code&gt;sf-helper.ts&lt;/code&gt; with no layering means every Salesforce change touches every file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Lessons Learned
&lt;/h2&gt;

&lt;p&gt;A few lessons only arrive after a suite has run against real orgs for a while. Sandbox refreshes reset data and sometimes configuration, so a suite that assumes seeded reference data breaks the morning after a refresh — seed defensively or create what you need. Field-level security and sharing rules differ between sandboxes and production, so a security test that passes in a permissive sandbox can give false confidence; test authorization in an environment that mirrors production access. Governor limits are shared across everything hitting the org, including other teams' integrations, so your suite's failures are sometimes caused by neighbors — correlation IDs and the org's API event monitoring are what let you prove it. And Bulk API result files can be large; stream and parse them rather than loading multi-megabyte CSVs into memory in a single worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Checklist
&lt;/h2&gt;

&lt;p&gt;Before calling a Salesforce API test framework production-ready, confirm each of the following. Authentication is centralized, cached, and secret-scrubbed. The API version is pinned and documented. Error handling classifies by Salesforce error code and distinguishes retryable from terminal. Retries use exponential backoff with jitter. Every asynchronous operation is validated by polling to a terminal state and reconciling results. Bulk tests assert on &lt;code&gt;failedResults&lt;/code&gt;, not just job state. Contract tests validate response schemas for every consumed object. Security tests cover the OWASP API Top 10 categories relevant to your org, and they fail closed. Tests are self-cleaning and data-isolated. CI runs the suite in parallel with managed secrets and publishes reports and traces on every run. Concurrency is capped to protect the shared org. Correlation IDs flow through logs and traces for post-mortem debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Failures and How to Handle Them
&lt;/h2&gt;

&lt;p&gt;Certain failures recur so often they deserve named handling. Token expiration mid-run is solved by the safety-window cache plus re-mint-on-401. The &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt; 403 (Salesforce's real throttling, not a 429) is solved by error-code-aware retry with backoff. True 429s and 503s on newer surfaces honor the &lt;code&gt;Retry-After&lt;/code&gt; header. Bulk upload failures are triaged by parsing &lt;code&gt;failedResults&lt;/code&gt; and re-submitting only transient rows. Invalid schema failures are caught early by contract tests rather than discovered downstream. Data mismatches across integrations are caught by seam-level reconciliation. Authentication failures are made debuggable by never swallowing the Salesforce error body. Timeouts and network instability are absorbed by bounded retries. Environment drift is prevented by typed, fail-fast configuration. The theme is that none of these are handled per test — they are handled once, in the framework, and every test inherits the resilience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Salesforce API Automation
&lt;/h2&gt;

&lt;p&gt;The direction of travel in 2026 is toward AI-assisted testing, and the useful version of it is narrower and more practical than the hype suggests.&lt;/p&gt;

&lt;p&gt;AI-assisted API testing today means using models to generate test scaffolding from a schema or an OpenAPI spec, to propose edge cases a human might miss, and to summarize failure clusters across a large run. The generation is a starting point that an engineer reviews, not an oracle. LLM-generated tests are most valuable for breadth — quickly covering the combinatorial space of field validations — while humans still own the high-value integration and security scenarios that require domain judgment.&lt;/p&gt;

&lt;p&gt;Self-healing is the more speculative frontier. For UI tests, self-healing locators are already mainstream. For APIs, the analog is a framework that detects a contract drift — a renamed field, a changed type — proposes the corresponding test update, and flags it for human approval rather than silently adapting, because an API contract change is usually a real event someone needs to know about, not noise to paper over.&lt;/p&gt;

&lt;p&gt;AI agents that plan and execute multi-step test workflows are emerging, and Salesforce's own platform is adding AI capabilities that themselves need testing. That last point is the durable one: as orgs adopt AI-driven automation and agent features, the surface that needs API-level validation grows, and the discipline in this article — auth, retries, contracts, reconciliation, observability — becomes more important, not less. The enterprise roadmap is less about replacing test engineers and more about engineers directing AI to cover more surface, faster, while keeping human judgment on the seams that matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Interview Questions
&lt;/h2&gt;

&lt;p&gt;These are the questions that actually separate engineers who have run Salesforce API automation in production from those who have only read about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Why is the JWT Bearer flow usually preferred over Authorization Code for a CI test framework?&lt;/strong&gt;&lt;br&gt;
JWT Bearer is headless and needs no user interaction or refresh-token storage. You sign an assertion with a private key and exchange it for an access token on demand, which fits CI perfectly. Authorization Code requires a browser redirect and a user session, which is awkward to automate and unnecessary when no human is present.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Salesforce returns a 403 with &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt;. Should you retry, and how?&lt;/strong&gt;&lt;br&gt;
Yes, but with backoff. It signals the rolling 24-hour API allocation is exhausted or nearly so. Retry with exponential backoff and jitter, and if the limit is genuinely hit, fail the run cleanly rather than hammering the org. Critically, do not treat every 403 this way — &lt;code&gt;INSUFFICIENT_ACCESS&lt;/code&gt; is also a 403 and must never be retried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Why should retry logic key on the Salesforce error code rather than the HTTP status?&lt;/strong&gt;&lt;br&gt;
Because Salesforce overloads HTTP statuses. Multiple distinct conditions share 403, and the real cause lives in the &lt;code&gt;errorCode&lt;/code&gt; field of the response body. Retrying on status alone conflates transient throttling with permanent permission failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What is wrong with asserting immediately after submitting a Bulk API 2.0 job?&lt;/strong&gt;&lt;br&gt;
Bulk 2.0 is asynchronous. The submission response tells you the job was accepted, not that any record was written. You must poll until the job reaches &lt;code&gt;JobComplete&lt;/code&gt;, &lt;code&gt;Failed&lt;/code&gt;, or &lt;code&gt;Aborted&lt;/code&gt;, then reconcile &lt;code&gt;successfulResults&lt;/code&gt; and &lt;code&gt;failedResults&lt;/code&gt;, because a completed job can still contain failed rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. How do you handle a token expiring in the middle of a long parallel run?&lt;/strong&gt;&lt;br&gt;
Cache the token with a safety window so you re-mint before the real expiry, and additionally re-mint on a 401 and retry the request once. A worker-scoped token service mints one session per worker rather than per test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Explain the difference between &lt;code&gt;/composite&lt;/code&gt;, &lt;code&gt;/composite/tree&lt;/code&gt;, and sObject Collections.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;/composite&lt;/code&gt; batches up to 25 subrequests that can reference each other's results, optionally atomic. &lt;code&gt;/composite/tree/{Object}&lt;/code&gt; inserts nested record trees up to 200 records. sObject Collections operate on up to 200 records of one object type in a single call. You choose based on whether you need cross-request references, nested trees, or homogeneous bulk-ish operations under REST.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. How would you test that field-level security is enforced?&lt;/strong&gt;&lt;br&gt;
Authenticate as a user without access to a sensitive field, query a record that has it, and assert the field is absent from the response. This maps to OWASP API3, Broken Object Property Level Authorization, on the data-exposure side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. What is mass assignment in a Salesforce context and how do you test for it?&lt;/strong&gt;&lt;br&gt;
Mass assignment is a client setting fields it should not control, such as &lt;code&gt;OwnerId&lt;/code&gt; or an audit field. Test it by attempting to set such a field as a restricted user and asserting the platform rejects or ignores the value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Why is fixed-interval retry dangerous at scale?&lt;/strong&gt;&lt;br&gt;
If many workers hit a limit simultaneously and retry on identical intervals, they retry in lockstep and re-trigger the limit, creating a thundering herd. Full jitter randomizes delays so load spreads out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. How do correlation IDs help in production test debugging?&lt;/strong&gt;&lt;br&gt;
A unique ID on every request lets you trace a single logical operation across client logs, retries, and — with API event monitoring enabled — server-side logs. When a test fails in CI, the correlation ID is the key that reconstructs exactly what happened without re-running anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. When is Playwright the wrong tool for Salesforce API performance testing?&lt;/strong&gt;&lt;br&gt;
For sustained, high-volume load testing. Playwright excels at latency budgets, concurrency behavior, and regression guardrails inside a functional suite, but purpose-built tools like k6 or Gatling are correct for sustained load with proper percentile reporting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. How do you test an asynchronous Salesforce-to-SAP integration through MuleSoft?&lt;/strong&gt;&lt;br&gt;
Create the record in Salesforce, poll the SAP side until the synced record appears (respecting the middleware's asynchronicity), then reconcile the payload across the seam. You assert at each boundary, never synchronously across an async hop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. What is the value of consumer-driven contract testing here?&lt;/strong&gt;&lt;br&gt;
It decouples provider and consumer release cycles. A consumer (say a MuleSoft flow) publishes its expectations to a broker; the Salesforce-side provider verifies them independently. Incompatibilities surface before either side deploys, without a full integration environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. How do you keep secrets out of Playwright traces and logs?&lt;/strong&gt;&lt;br&gt;
Traces capture request bodies and headers, so scrub the &lt;code&gt;Authorization&lt;/code&gt; header and any token before logging, source secrets from a managed store read once per process, and never write raw keys to artifacts. Treat the trace as a potential leak surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. Why pin the Salesforce API version instead of always using the latest?&lt;/strong&gt;&lt;br&gt;
Salesforce supports each version for a multi-year window. Pinning means a release upgrade cannot silently change response shapes under your assertions. You upgrade deliberately, run the suite, then move forward, converting an ambush into a controlled change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;16. What does &lt;code&gt;allOrNone&lt;/code&gt; do in a Composite request and why test the false path?&lt;/strong&gt;&lt;br&gt;
With &lt;code&gt;allOrNone: true&lt;/code&gt;, a failure in any subrequest rolls back all of them. You test both paths because partial-commit behavior (&lt;code&gt;false&lt;/code&gt;) is exactly what corrupts data — you must know and assert which mode your integration relies on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. How do you retry a partially failed Bulk job correctly?&lt;/strong&gt;&lt;br&gt;
Parse the &lt;code&gt;failedResults&lt;/code&gt; CSV, separate transient errors (row locks, storage) from permanent ones (validation), build a new CSV of only the transient failures, and submit a fresh job. Re-running the whole file risks duplicating rows that already succeeded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What is BOLA and how does it map to Salesforce?&lt;/strong&gt;&lt;br&gt;
Broken Object Level Authorization, OWASP's top API risk, is accessing an object you should not be allowed to. In Salesforce it maps to record-level sharing. Test it by attempting to read a restricted record as a low-privilege user and asserting a 403 or 404.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. How should CI concurrency be bounded for a shared org?&lt;/strong&gt;&lt;br&gt;
Cap Playwright &lt;code&gt;workers&lt;/code&gt; to what the org can absorb, use a dedicated integration user for attributable consumption, and shard across CI jobs to spread rather than concentrate load. Uncontrolled parallelism can exhaust the daily allocation and break unrelated integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. How do you prevent test data collisions across parallel workers?&lt;/strong&gt;&lt;br&gt;
Namespace all created data with a unique run or worker ID embedded in record names or external IDs, and make every test self-cleaning. Assertions then match only their own data, and parallel workers never interfere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Can Playwright really replace Postman or REST Assured for Salesforce API testing?&lt;/strong&gt;&lt;br&gt;
For most teams, yes. Playwright's &lt;code&gt;APIRequestContext&lt;/code&gt; is a full HTTP client with fixtures, tracing, retries, parallelism, and unified reporting, and it lives in the same TypeScript runtime as any UI tests. Postman remains better for exploratory, GUI-driven work and REST Assured for JVM shops, but for a codified, CI-run Salesforce suite, Playwright is a strong default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Do I need a browser for Playwright API tests?&lt;/strong&gt;&lt;br&gt;
No. API tests use &lt;code&gt;APIRequestContext&lt;/code&gt; directly and need no browser download. In CI you can skip the browser install entirely, which makes the job faster and lighter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Which OAuth flow should my automated suite use against Salesforce?&lt;/strong&gt;&lt;br&gt;
JWT Bearer in almost all cases. It is headless, needs no refresh-token storage, and suits CI. Use Client Credentials when you specifically want a run-as-user service context with no impersonation of a named user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. How do I handle Salesforce's daily API request limits in a large suite?&lt;/strong&gt;&lt;br&gt;
Cache tokens, cap concurrency, use a dedicated integration user, shard load across CI jobs, and implement error-code-aware backoff on &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt;. Treat the org as a shared tenant with a finite budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Why does Salesforce not always return a 429 for rate limiting?&lt;/strong&gt;&lt;br&gt;
Salesforce predates the widespread 429 convention and encodes throttling in error codes. The classic daily-limit response is a 403 with &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt;. Some newer platform surfaces do emit a true 429 with &lt;code&gt;Retry-After&lt;/code&gt;, so handle both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What is the right way to test Bulk API 2.0?&lt;/strong&gt;&lt;br&gt;
Create the ingest job, upload CSV, mark it complete, poll to a terminal state, then reconcile &lt;code&gt;successfulResults&lt;/code&gt;, &lt;code&gt;failedResults&lt;/code&gt;, and &lt;code&gt;unprocessedRecords&lt;/code&gt;. Never trust &lt;code&gt;JobComplete&lt;/code&gt; alone as proof of success.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. How do I validate large data migrations?&lt;/strong&gt;&lt;br&gt;
Use Bulk 2.0 for volume, assert row counts across success and failure result sets, and reconcile a sample (or all) of the migrated records against the source. Stream large result CSVs rather than loading them fully into memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. How should I store the JWT private key in CI?&lt;/strong&gt;&lt;br&gt;
As a secret in the runner's secret store, written to a file in a pre-step and referenced by path. Never commit it. Rotate by updating the secret and the Connected App certificate, with no code change required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. What is the difference between the &lt;code&gt;request&lt;/code&gt; fixture and &lt;code&gt;playwright.request.newContext()&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;request&lt;/code&gt; fixture gives a ready-made context per test. &lt;code&gt;newContext()&lt;/code&gt; creates a longer-lived context you control, useful for a shared authenticated client with a fixed base URL and headers across a file or worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. How do I test authorization and sharing rules?&lt;/strong&gt;&lt;br&gt;
Authenticate as users with different permission sets and assert each can only access what they should. Restricted reads should return 403 or 404, restricted fields should be absent, and restricted writes should be rejected. These are negative tests that must fail closed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. Can I test Platform Events and Change Data Capture with Playwright?&lt;/strong&gt;&lt;br&gt;
Playwright is HTTP-oriented, so for streaming you typically pair it with a CometD or event-bus client: subscribe downstream, trigger the change via Playwright's REST calls, and assert delivery. The trigger-and-verify pattern still applies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. How do I keep tests from interfering with each other in parallel?&lt;/strong&gt;&lt;br&gt;
Isolate and namespace test data with unique run IDs, make every test self-cleaning, and avoid shared mutable state. Design so no test depends on another's side effects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. Should security tests live in the same suite as functional tests?&lt;/strong&gt;&lt;br&gt;
Yes. Mapping a handful of OWASP API Top 10 checks into the same suite means they run on every build and regressions surface immediately, rather than waiting for a periodic pen test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. How do I validate response schemas?&lt;/strong&gt;&lt;br&gt;
Compile JSON Schemas with a validator like Ajv and assert every important response against its schema. For custom endpoints and middleware, drive validation from an OpenAPI specification so drift fails CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. What is the role of Pact in a Salesforce landscape?&lt;/strong&gt;&lt;br&gt;
Pact enables consumer-driven contract testing between Salesforce and the services that integrate with it. Consumers publish expectations; providers verify them independently, catching interface disagreements before deployment without a full integration environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;16. How do I make failures debuggable in CI?&lt;/strong&gt;&lt;br&gt;
Carry correlation IDs on every request, enable Playwright tracing on failure, emit structured secret-scrubbed logs, and publish reports and traces as artifacts on every run. Together these reconstruct any failure without re-running it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. How do I test integrations with SAP, Oracle, or payment gateways?&lt;/strong&gt;&lt;br&gt;
Treat each external system as another HTTP client with its own auth. Create data on one side through Salesforce, poll the other side for the synced result, and reconcile the payload. Assert at each seam and then end to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What API version should I target in 2026?&lt;/strong&gt;&lt;br&gt;
Pin to a specific recent version — Winter '26 shipped as v65.0 and Spring '26 as v66.0 — and document it. Upgrade deliberately after running the suite against the new version, rather than always chasing the newest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. How do I handle timeouts and network instability?&lt;/strong&gt;&lt;br&gt;
Set sensible per-request timeouts, wrap requests in bounded retries with backoff, and classify transient failures as retryable. Do not retry indefinitely; fail cleanly after the cap so a genuinely broken environment surfaces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. Can I mix UI and API tests in one Playwright project?&lt;/strong&gt;&lt;br&gt;
Yes, that is a core Playwright strength. You can authenticate via API, reuse the session state in a browser context, and assert backend side effects and UI behavior in one suite. For Salesforce, API-first setup makes UI tests far faster and less flaky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21. How do I avoid governor-limit surprises during test runs?&lt;/strong&gt;&lt;br&gt;
Understand which limits your suite exercises (API requests, concurrent long-running requests, Bulk record limits), keep concurrency within budget, and monitor consumption. A dedicated integration user makes your suite's usage attributable and easier to reason about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22. Is it safe to run the suite against production?&lt;/strong&gt;&lt;br&gt;
Run functional and destructive tests against sandboxes. Reserve production for carefully scoped, read-mostly smoke checks with strong data isolation. Never run heavy Bulk or destructive tests against a live production org.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23. How do I test the Salesforce GraphQL API?&lt;/strong&gt;&lt;br&gt;
Send POST requests to the GraphQL endpoint with a query, and contract-test the response shape. Because the client defines the shape, assert on over-fetching and under-fetching explicitly, and validate that field-level security still applies within GraphQL responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24. How does AI fit into Salesforce API testing today?&lt;/strong&gt;&lt;br&gt;
Practically, AI helps generate test scaffolding from schemas, propose edge cases, and cluster failures. Engineers review the output and own the high-judgment integration and security scenarios. As orgs adopt AI and agent features, the surface needing disciplined API testing grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25. What is the single most common mistake in Salesforce API test frameworks?&lt;/strong&gt;&lt;br&gt;
Treating Salesforce like a generic REST API. The governor limits, non-standard throttling responses, asynchronous jobs, and multi-flow authentication all demand Salesforce-specific handling. Frameworks that ignore this look fine until the first real load or the first release upgrade, then fail in ways generic assumptions cannot explain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;These are genuine, authoritative references worth keeping close.&lt;/p&gt;

&lt;p&gt;Salesforce REST API Developer Guide — developer.salesforce.com/docs (REST API atlas)&lt;/p&gt;

&lt;p&gt;Salesforce Bulk API 2.0 Developer Guide — developer.salesforce.com/docs (Bulk API atlas)&lt;/p&gt;

&lt;p&gt;Salesforce Composite and Connect REST resources — developer.salesforce.com/docs&lt;/p&gt;

&lt;p&gt;Salesforce OAuth and Connected Apps documentation — help.salesforce.com and the Identity implementation guides&lt;/p&gt;

&lt;p&gt;Playwright Documentation, including API testing and &lt;code&gt;APIRequestContext&lt;/code&gt; — playwright.dev&lt;/p&gt;

&lt;p&gt;OAuth 2.0 Authorization Framework — RFC 6749&lt;/p&gt;

&lt;p&gt;OAuth 2.0 Bearer Token Usage — RFC 6750&lt;/p&gt;

&lt;p&gt;JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants — RFC 7523&lt;/p&gt;

&lt;p&gt;OpenAPI Specification — spec.openapis.org&lt;/p&gt;

&lt;p&gt;JSON Schema — json-schema.org&lt;/p&gt;

&lt;p&gt;OWASP API Security Top 10 (2023) — owasp.org/API-Security&lt;/p&gt;

&lt;p&gt;Pact (consumer-driven contract testing) — docs.pact.io&lt;/p&gt;

&lt;p&gt;Postman Learning Center — learning.postman.com&lt;/p&gt;

&lt;p&gt;TypeScript Documentation — typescriptlang.org/docs&lt;/p&gt;

&lt;p&gt;Node.js Documentation — nodejs.org/docs&lt;/p&gt;

&lt;p&gt;Google SRE Book — sre.google/books&lt;/p&gt;

&lt;p&gt;Martin Fowler on testing and integration (ContractTest, TestPyramid) — martinfowler.com&lt;/p&gt;

&lt;p&gt;Microsoft REST API Guidelines — github.com/microsoft/api-guidelines&lt;/p&gt;

&lt;p&gt;Azure Architecture Center — learn.microsoft.com/azure/architecture&lt;/p&gt;

&lt;p&gt;AWS Well-Architected Framework — aws.amazon.com/architecture/well-architected&lt;/p&gt;

&lt;h2&gt;
  
  
  HimanshuAI August Sale — FLAT 95% OFF
&lt;/h2&gt;

&lt;p&gt;The HimanshuAI August Sale is now live.&lt;/p&gt;

&lt;p&gt;For a limited time, get FLAT 95% OFF on my complete collection of premium AI Engineering digital playbooks.&lt;/p&gt;

&lt;p&gt;New Bundles:&lt;/p&gt;

&lt;p&gt;• GenAI Engineering Vault — 16 Books&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• THE BUNDLE — LLM &amp;amp; Generative AI Testing Pro&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Coding Agents Mastery — Volume 1&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Ollama &amp;amp; Local LLMs — Complete 4 Book Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AWS Cloud Tester Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Salesforce Automation Testing Mastery Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Playwright + TypeScript Mastery Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coupon Code&lt;/p&gt;

&lt;p&gt;AI95&lt;/p&gt;

&lt;p&gt;Flat 95% OFF&lt;/p&gt;

&lt;p&gt;Explore&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Salesforce API testing done well is a discipline, not a folder of scripts. The platform's realities — governor limits, non-standard throttling responses, asynchronous Bulk and event APIs, and a six-flow authentication surface — mean a generic REST testing mindset breaks the moment it meets real load or a release upgrade. Everything in this article was aimed at replacing that mindset with an engineered one.&lt;/p&gt;

&lt;p&gt;The framework we built layers responsibilities cleanly. A cached, observable token service treats authentication as infrastructure. A request layer over Playwright's &lt;code&gt;APIRequestContext&lt;/code&gt; owns headers, correlation IDs, structured logging, and error handling. A retry policy with exponential backoff and full jitter, keyed on Salesforce error codes rather than raw HTTP status, absorbs transient failure without hammering the org. Domain helpers keep business-facing tests readable, and fixtures inject a fully authenticated client so no test ever touches a token.&lt;/p&gt;

&lt;p&gt;On top of that foundation, REST tests cover CRUD, pagination, and business rules; Composite tests cover atomic dependent creation; Bulk tests orchestrate the full asynchronous lifecycle and — crucially — reconcile per-row results instead of trusting job state. Contract tests guard response shapes with JSON Schema and OpenAPI, and consumer-driven contracts with Pact decouple release cycles across teams. Security tests map to the OWASP API Top 10 (2023) and fail closed. Performance guardrails catch regressions inside the functional suite. Integration tests assert at every seam between Salesforce and SAP, Oracle, MuleSoft, Kafka, Azure, AWS, and payment systems, always polling across asynchronous boundaries rather than assuming synchrony. CI runs it all in parallel, with managed secrets, capped concurrency, and reports and traces published on every run.&lt;/p&gt;

&lt;p&gt;The payoff is a suite you can operate at 3 a.m., not just run at noon: every failure carries a correlation ID, a trace, and a classified error, so the path from red build to root cause is short. As AI-assisted testing matures, this discipline becomes more valuable, not less — because AI can generate breadth, but the seams, the contracts, and the authorization boundaries still need engineered judgment. Build the foundation once, and every future test, human- or AI-authored, inherits its resilience.&lt;/p&gt;




&lt;p&gt;Written by Himanshu Agarwal&lt;/p&gt;

&lt;p&gt;Enterprise Test Architect&lt;/p&gt;

&lt;p&gt;AI Engineering Author&lt;/p&gt;

&lt;p&gt;Generative AI Educator&lt;/p&gt;

&lt;p&gt;Automation Architect&lt;/p&gt;




</description>
      <category>salesforce</category>
      <category>ai</category>
      <category>api</category>
      <category>typescript</category>
    </item>
    <item>
      <title># GitHub Copilot Enterprise Mastery (2026 Edition)</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Fri, 07 Aug 2026 10:05:59 +0000</pubDate>
      <link>https://dev.to/himanshuai/-github-copilot-enterprise-mastery-2026-edition-4dkk</link>
      <guid>https://dev.to/himanshuai/-github-copilot-enterprise-mastery-2026-edition-4dkk</guid>
      <description>&lt;h2&gt;
  
  
  Enterprise AI Coding, Security, Governance &amp;amp; Developer Productivity
&lt;/h2&gt;

&lt;p&gt;Written by Himanshu Agarwal&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot stopped being "the thing that autocompletes your code" some time ago. By 2026 it is a multi-model, multi-surface engineering platform that spans inline completions, conversational chat, agent mode inside the IDE, an autonomous coding agent that turns issues into pull requests, a command-line agent, and a control plane that orchestrates agents from multiple vendors. For an individual developer this is a productivity tool. For an enterprise it is a new tier of the software development lifecycle that touches security, compliance, governance, IP risk, procurement, and platform engineering all at once.&lt;/p&gt;

&lt;p&gt;This guide is written for engineers and leaders who already know how to ship software and now have to decide how AI-assisted engineering fits into a large organization. It is not a walkthrough of installing an extension. It assumes you operate at scale: hundreds or thousands of repositories, regulated data, audited pipelines, mixed cloud estates, and a mandate to raise throughput without lowering quality or increasing risk.&lt;/p&gt;

&lt;p&gt;The framing throughout is deliberately architectural. Copilot is not one product but a set of surfaces with different execution contexts, different data-handling guarantees, and different governance controls. Treating them as interchangeable is the most common and most expensive mistake enterprises make. A completion generated inline in VS Code, a chat answer grounded in an indexed private repository, and an autonomous agent run that opens a pull request are three very different things from a security and accountability standpoint, even when the same underlying model is involved.&lt;/p&gt;

&lt;p&gt;We will move from the "why" through the internal architecture, then into the operational disciplines that determine whether an enterprise rollout succeeds: prompt engineering as a team skill, security and content boundaries, compliance posture, governance and administration, productivity engineering across the stack, AI-assisted testing, automated code review, integration with existing CI/CD and DevSecOps tooling, and the responsible-AI practices that keep humans accountable for what ships. The final third of the document is a practical reference: real enterprise scenarios, common failure modes, twenty-five advanced interview questions, thirty in-depth FAQs, a curated resource list, and an adoption checklist.&lt;/p&gt;

&lt;p&gt;Where product details are stated, they reflect the 2026 landscape: a model marketplace that includes OpenAI, Anthropic, Google, xAI, and Microsoft models; agent mode generally available in VS Code and JetBrains; a coding agent that operates issue-to-PR; a GA command-line agent; and enterprise controls around data residency, content exclusion, telemetry export, and policy management. Product surfaces move quickly, so treat specific model names and dates as a snapshot and verify the current supported-models and trust documentation before you finalize procurement or policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why GitHub Copilot Is Transforming Enterprise Engineering
&lt;/h2&gt;

&lt;p&gt;The transformation is not about typing speed. Autocomplete was the 2021 story. The 2026 story is that the unit of AI assistance has moved up the abstraction ladder from the token to the task.&lt;/p&gt;

&lt;p&gt;Three shifts matter to an enterprise architect.&lt;/p&gt;

&lt;p&gt;The first is agentic execution. Copilot's agent mode can determine which files to edit across a change, run terminal commands, execute tests, read the failures, and iterate, all inside a single instruction. The coding agent goes further: you assign a GitHub issue to Copilot and it works in the background, produces a branch, and opens a pull request for human review. This changes the shape of work. Engineers increasingly spend their time specifying intent, reviewing generated diffs, and owning outcomes rather than producing every line by hand. That is a different job description, and it demands different guardrails.&lt;/p&gt;

&lt;p&gt;The second is grounding. Copilot Business gives you governance and IP indemnity on top of completions and chat. Copilot Enterprise adds codebase-aware answers grounded in your indexed private repositories, plus curated knowledge context and Copilot inside GitHub.com. The difference between a model that knows general patterns and a system that answers in terms of your actual conventions, internal libraries, and service boundaries is the difference between a clever intern and a colleague who has read your monorepo. For large proprietary codebases, grounding is where the recurring value lives.&lt;/p&gt;

&lt;p&gt;The third is the multi-model reality. Copilot is now a model marketplace rather than a single model. Chat and agent surfaces expose a picker spanning OpenAI, Anthropic, Google, and other providers, with an auto-selection mode and organization policies that constrain which models are permitted. This is strategically important: enterprises are no longer locked to a single vendor's model behavior, and platform teams can standardize on models that fit their latency, cost, and quality profile while retaining a single subscription, single audit surface, and single set of policy controls.&lt;/p&gt;

&lt;p&gt;The strategic consequence is that AI-assisted engineering becomes a platform concern rather than an individual purchase. When completions are ungoverned, an organization accumulates invisible risk: proprietary code pasted into prompts, unreviewed generated logic in production, inconsistent quality, and no audit trail. When the same capability is deployed through Business or Enterprise with policy controls, content exclusion, usage analytics, and IP indemnity, it becomes an asset that a platform team can measure, tune, and defend. The difference between those two outcomes is governance, and governance is the theme that runs through everything that follows.&lt;/p&gt;

&lt;h2&gt;
  
  
  HimanshuAI August Sale — FLAT 95% OFF
&lt;/h2&gt;

&lt;p&gt;The HimanshuAI August Sale is now live.&lt;/p&gt;

&lt;p&gt;For a limited time, get FLAT 95% OFF on my complete collection of premium AI Engineering digital playbooks.&lt;/p&gt;

&lt;p&gt;New Bundles&lt;/p&gt;

&lt;p&gt;• GenAI Engineering Vault — 16 Books&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• THE BUNDLE — LLM &amp;amp; Generative AI Testing Pro&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Coding Agents Mastery — Volume 1&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Ollama &amp;amp; Local LLMs — Complete 4 Book Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AWS Cloud Tester Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Salesforce Automation Testing Mastery Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Playwright + TypeScript Mastery Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coupon Code&lt;/p&gt;

&lt;p&gt;AI95&lt;/p&gt;

&lt;p&gt;Flat 95% OFF&lt;/p&gt;

&lt;p&gt;Explore&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  GitHub Copilot Editions
&lt;/h2&gt;

&lt;p&gt;Copilot is sold as a family of plans, and choosing the wrong tier is a governance decision disguised as a pricing decision. As of 2026 the lineup spans individual and organizational plans.&lt;/p&gt;

&lt;p&gt;The individual plans are Copilot Free, Copilot Pro, Copilot Pro+, and Copilot Max. Free provides limited completions and chat with auto model selection. Pro is the standard paid individual plan. Pro+ raises premium-request allowances and unlocks broader model access and features such as natural-language app building. Max targets sustained high-volume power users with the largest individual pool of AI credits and priority access to new models. These plans are appropriate for solo developers and open-source maintainers, but they carry an important caveat for enterprises: interaction data from personal plans can, by default, be used to improve models unless the user opts out. That alone disqualifies personal plans for proprietary work.&lt;/p&gt;

&lt;p&gt;The organizational plans are Copilot Business and Copilot Enterprise, and this is where every serious deployment lives.&lt;/p&gt;

&lt;p&gt;Copilot Business is aimed at organizations on GitHub Free or Team plans, or enterprises on GitHub Enterprise Cloud. It includes completions and chat, agent mode, the cloud coding agent, access to a broad model catalog, a monthly pool of AI credits, centralized license management, and organization-level policy control. Critically, Business customers are contractually excluded from having their interaction data used for model training, and the plan carries IP indemnity through GitHub's Copilot Copyright Commitment. For most teams that need governance, privacy, and administrative control, Business is the correct baseline.&lt;/p&gt;

&lt;p&gt;Copilot Enterprise is for organizations on GitHub Enterprise Cloud. It includes everything in Business and adds the capabilities that only matter at scale: codebase-aware chat grounded in your indexed repositories, Copilot embedded in the GitHub.com experience, knowledge bases that provide curated documentation as grounding context, pull request summaries, priority access to new models, and a larger monthly allowance of AI credits. The premium over Business is justified specifically when "grounded in our own code and conventions" produces repeated, measurable time savings across a large proprietary codebase.&lt;/p&gt;

&lt;p&gt;A billing nuance worth understanding: seat prices cover unlimited everyday code completions, while premium AI features (chat, agents, code review) are metered through a monthly allowance of AI credits, with usage beyond the allowance billed on a usage basis. Business and Enterprise seats include different monthly allowances. This matters for capacity planning: a team that lives in agent mode all day has a very different consumption profile from a team that mostly uses inline completions, and cost centers and per-user budgets let platform teams control that.&lt;/p&gt;

&lt;p&gt;Selection guidance for an enterprise architect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never use personal plans for proprietary code. The data-training default and lack of centralized policy control make them unacceptable for regulated or IP-sensitive work.&lt;/li&gt;
&lt;li&gt;Start on Business when you need governance, IP indemnity, privacy guarantees, and administration, but do not yet have a proven, recurring need for codebase-grounded answers.&lt;/li&gt;
&lt;li&gt;Move to Enterprise when you can name the specific, repeated workflows where answers grounded in your indexed repositories and internal knowledge save real time, and when you are already standardized on GitHub Enterprise Cloud.&lt;/li&gt;
&lt;li&gt;Budget for AI credits separately from seats, and instrument consumption from day one so cost does not surprise finance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Architecture of GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;To govern Copilot you have to understand what happens between a keystroke and a suggestion. The system is best understood as a set of surfaces sharing a common model marketplace, each surface with its own context assembly pipeline and its own data-handling contract.&lt;/p&gt;
&lt;h3&gt;
  
  
  The LLMs Behind Copilot
&lt;/h3&gt;

&lt;p&gt;Copilot in 2026 is a model marketplace rather than a single model. The catalog spans multiple providers: OpenAI models (the GPT-5 family and Codex-specialized variants), Anthropic Claude models (Sonnet, Opus, and Haiku classes), Google Gemini models (Pro and Flash variants), xAI Grok, and Microsoft's own coding-oriented models. Surfaces expose a model picker in chat and agent mode, plus an Auto mode that selects a model per task. Availability is not uniform: the same model name is not present on every surface, and Auto is not a single global pool. Chat, CLI, and the coding agent each have their own Auto membership, and organization policy can narrow the list further.&lt;/p&gt;

&lt;p&gt;The practical implications for an enterprise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inline completions use a model configured by the organization admin. Individual users cannot pick the completion model. This is a deliberate control point.&lt;/li&gt;
&lt;li&gt;Chat and agent surfaces allow model selection, subject to org policy. Platform teams should define which models are permitted, especially where data-handling commitments differ by provider.&lt;/li&gt;
&lt;li&gt;Model hosting and data commitments vary. Anthropic, Google, and xAI models run under specific hosting and retention terms; some run under zero-data-retention API policies, and prompts continue to pass through Copilot's content filters for public-code matching and harmful content regardless of provider. Read the model-hosting documentation before enabling a provider organization-wide.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Prompt Flow
&lt;/h3&gt;

&lt;p&gt;Every Copilot interaction is a context-assembly problem. The visible prompt (your comment, chat message, or issue) is only part of what the model receives. Copilot builds a request from multiple sources: the current file, open editors, cursor position, neighboring symbols, imports, and, on grounded surfaces, indexed repository content and curated knowledge. It then applies content exclusions to remove restricted paths, runs filters, sends the assembled request to the selected model, and post-processes the response, including a public-code matching filter that can suppress suggestions resembling public code.&lt;/p&gt;

&lt;p&gt;Understanding this flow explains most "why did it do that" moments. A weak suggestion usually means weak assembled context, not a weak model. The lever you control is what enters the context window, which is why prompt engineering and context hygiene are team skills, not personal quirks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Context Window and Repository Context
&lt;/h3&gt;

&lt;p&gt;The context window is finite, and on large repositories the binding constraint is relevance, not raw size. Copilot prioritizes signals it can cheaply access: the active file, recently viewed files, and symbols in scope. On Enterprise, repository indexing makes broader codebase context retrievable so that chat answers can be grounded in your actual code rather than generic patterns.&lt;/p&gt;

&lt;p&gt;Repository indexing is the mechanism behind codebase-aware chat. When a repository is indexed, semantic search over the codebase lets Copilot retrieve the most relevant files for a question and ground its answer in them. For a large proprietary system this is the single most valuable Enterprise capability, and it is also the capability most sensitive to code organization: well-factored code with clear module boundaries indexes and retrieves better than a tangled monolith.&lt;/p&gt;
&lt;h3&gt;
  
  
  Workspace Awareness and Grounding Context
&lt;/h3&gt;

&lt;p&gt;Beyond a single file, Copilot can reason about a broader working set. Curated context bundles let teams assemble the files, issues, pull requests, and documentation that should ground answers for a given domain, so that a question about, say, the payments service is answered with the payments service's real code and docs in context. This is how you get answers that respect internal conventions instead of inventing plausible-but-wrong patterns.&lt;/p&gt;
&lt;h3&gt;
  
  
  Chat Mode
&lt;/h3&gt;

&lt;p&gt;Chat is the conversational surface. It runs in the IDE and on GitHub.com, supports model selection, and can be grounded in repository context on Enterprise. Chat is where explanation, targeted generation, debugging, and refactoring conversations happen. It is stateful within a session but has no memory across sessions unless a persistent context mechanism is used, so effective use means supplying the right grounding each time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Agent Mode
&lt;/h3&gt;

&lt;p&gt;Agent mode, generally available in VS Code and JetBrains, elevates chat from suggestion to execution. Given a goal, the agent determines which files to change, proposes multi-file edits, can run terminal commands and tests, reads failures, and iterates toward a working state. It uses the model currently selected in chat. Agent mode keeps the human in the loop at the IDE: you see the plan, approve edits, and review the diff before committing.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Coding Agent
&lt;/h3&gt;

&lt;p&gt;The coding agent is a hosted, asynchronous service, distinct from IDE agent mode. You assign a GitHub issue (or launch a task) to Copilot, and it works in the background in an isolated environment: it writes code, runs tests, and opens a pull request for review. Because it is a hosted service running async workflows, it has its own model picker per task, separate from the IDE chat selector. The coding agent only operates on repositories hosted on GitHub. Governance here is the pull request itself: nothing merges without human review, branch protection, required checks, and code owners still apply. Treat coding-agent PRs exactly like human PRs, because the accountability model depends on it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Command-Line Agent
&lt;/h3&gt;

&lt;p&gt;The Copilot CLI is a separate install that brings agentic workflows to the terminal. It offers a planning mode, an autonomous mode, specialized sub-agents for exploration, tasks, code review, and planning, repository memory across sessions, hooks and plugins, and a built-in GitHub MCP server. For DevOps, SRE, and platform engineers who live in the shell, the CLI agent is often the highest-leverage surface.&lt;/p&gt;
&lt;h3&gt;
  
  
  Copilot Spaces, Knowledge, and Agent Orchestration
&lt;/h3&gt;

&lt;p&gt;Two enterprise-facing concepts anchor grounding and orchestration. Curated context spaces bundle files, issues, PRs, and documentation as reusable grounding for any Copilot surface, which is how a team encodes "the context you need to answer questions about this domain." Knowledge bases on Enterprise provide curated documentation as retrievable grounding. Above these sits an agent-orchestration control plane that lets an organization run agents from multiple vendors across GitHub, the IDE, the CLI, and mobile under a single Copilot subscription, single audit surface, and single policy layer. The strategic message is that Copilot positions itself as the interface for coding agents generally, not only the home of one vendor's agent.&lt;/p&gt;
&lt;h3&gt;
  
  
  Customization and Instructions
&lt;/h3&gt;

&lt;p&gt;Copilot's behavior is shaped by instruction files that live in the repository and travel with it. A repository-level instructions file (for example &lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;) encodes conventions, preferred libraries, and constraints that apply to every interaction in that repo. Personal and organization-level instructions layer on top. Agent-definition files let teams define custom agents with specific tool sets and behavior per project. These files are the primary mechanism for making Copilot follow your standards, and they belong in version control and code review like any other source of truth.&lt;/p&gt;

&lt;p&gt;The architectural takeaway: Copilot is a layered system where the model is the least differentiating part. What differentiates enterprise outcomes is context assembly, grounding, instruction files, policy controls, and the review gates around agentic surfaces.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prompt Engineering for Developers
&lt;/h2&gt;

&lt;p&gt;Prompt engineering inside an IDE is not the same discipline as prompting a chatbot. The model already has substantial context from your editor; your job is to supply the missing intent, constraints, and acceptance criteria precisely, and to manage what enters the context window.&lt;/p&gt;
&lt;h3&gt;
  
  
  Writing Better Prompts
&lt;/h3&gt;

&lt;p&gt;The reliable pattern is intent, constraints, and shape. State what you want, the constraints that bound acceptable solutions, and the shape of the expected output.&lt;/p&gt;

&lt;p&gt;A weak prompt: "add caching here."&lt;/p&gt;

&lt;p&gt;A strong prompt, written as a comment above the function or as a chat message with the file open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;through&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nf"&gt;getUserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="nx"&gt;Constraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Use&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="nx"&gt;RedisClient&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;TTL&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user:profile:{userId}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;On&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="nx"&gt;miss&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUserProfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="nx"&gt;populate&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;On&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;fall&lt;/span&gt; &lt;span class="nx"&gt;back&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;fail&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="nx"&gt;because&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Preserve&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;signature&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="nx"&gt;Return&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;modified&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strong prompt removes ambiguity about dependencies, key format, failure behavior, and output shape. Copilot's suggestion quality tracks the specificity of constraints far more than the eloquence of the request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Chaining
&lt;/h3&gt;

&lt;p&gt;Complex changes are decomposed into a sequence where each step's output becomes the next step's grounded input. Rather than asking for an entire feature at once, chain: first ask Copilot to outline the change across files, review and correct the plan, then implement one module, then generate tests for that module, then wire integration. Chaining keeps each step small enough to review honestly and small enough to fit useful context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context Management
&lt;/h3&gt;

&lt;p&gt;Because context is finite and relevance-driven, curate it deliberately. Open the files that matter and close the noise. Reference specific symbols and files explicitly in chat. Use instruction files to encode standing conventions so you do not repeat them in every prompt. On Enterprise, ensure the relevant repositories are indexed and, where available, assemble a curated context space for the domain so answers are grounded in real code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codebase Understanding
&lt;/h3&gt;

&lt;p&gt;One of the highest-value uses of chat is comprehension of unfamiliar code. Ask for an explanation of a module's responsibilities, the data flow through a request, the invariants a class assumes, or the blast radius of a proposed change. On grounded surfaces these answers reference your actual code. This is transformative for onboarding, legacy modernization, and incident response, where the bottleneck is understanding, not typing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Security
&lt;/h2&gt;

&lt;p&gt;Security posture is where Copilot deployments are won or lost. The controls exist, but they are off or permissive by default in several places, so a secure deployment is an act of configuration, not an assumption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Privacy
&lt;/h3&gt;

&lt;p&gt;The single most important fact for an enterprise: on Copilot Business and Enterprise, your prompts and suggestions are not used to train models, and code sent for completions is discarded after processing rather than retained for training. This is a contractual commitment, not a best-effort promise, and it is the reason personal plans are unacceptable for proprietary work. Personal plans (Free, Pro, Pro+) default to allowing interaction data to be used for model improvement unless the user opts out; Business and Enterprise are explicitly excluded from that pipeline.&lt;/p&gt;

&lt;p&gt;The operational rule that follows: standardize the entire organization on Business or Enterprise, and block personal-plan use of corporate identities and repositories through policy and SSO.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telemetry
&lt;/h3&gt;

&lt;p&gt;Copilot collects telemetry: usage metrics, IDE interaction signals such as whether a suggestion was accepted or dismissed, and performance data. This telemetry powers usage analytics and is distinct from the code content itself. Enterprises with strict requirements can, in 2026, use managed telemetry export (including OpenTelemetry export for the IDE and CLI) so that usage signals flow into the organization's own observability stack under its own retention rules. Understand what telemetry is collected, where it is processed, and how long it is retained, and document it for your data-protection impact assessment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policy Controls
&lt;/h3&gt;

&lt;p&gt;Organization and enterprise administrators control Copilot behavior through policies: which surfaces are enabled, which models are permitted, whether suggestions matching public code are allowed, whether the coding agent and code review are enabled, and more. Policies are the enforcement layer for everything else in this section. A representative baseline for a regulated organization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block suggestions matching public code, to reduce license-contamination risk.&lt;/li&gt;
&lt;li&gt;Restrict the permitted model list to providers whose data-handling terms your legal team has approved.&lt;/li&gt;
&lt;li&gt;Enable content exclusion for sensitive repositories and paths.&lt;/li&gt;
&lt;li&gt;Keep the coding agent and code review off by default and enable them per-team as those teams demonstrate readiness.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Content Exclusion
&lt;/h3&gt;

&lt;p&gt;Content exclusion lets administrators configure repositories and paths that Copilot must never read as context or use for suggestions. This is the mechanism that keeps secrets, sensitive configuration, regulated data schemas, and proprietary algorithms out of the context window entirely. Two caveats every architect should internalize: exclusions are configured and enforced at the organization or repository level through GitHub's admin surfaces, not through a client-side ignore file, and a client-side &lt;code&gt;.copilotignore&lt;/code&gt; is not an officially enforced server-level control. Configure exclusions centrally so they cannot be bypassed by an individual's editor settings.&lt;/p&gt;

&lt;p&gt;Content exclusion pairs with secret scanning for defense in depth: exclusion keeps sensitive files out of context, while secret scanning catches credentials that leak into the repository in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repository Permissions
&lt;/h3&gt;

&lt;p&gt;Copilot respects the permission model of GitHub. A user's grounded answers and coding-agent actions are bounded by what that identity can access. This means your existing access controls, team boundaries, and repository visibility are load-bearing for Copilot security. Before enabling grounded chat or the coding agent broadly, audit repository permissions, because Copilot will faithfully surface anything a permitted identity can already reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance
&lt;/h2&gt;

&lt;p&gt;Compliance is where security posture meets auditor expectations. Copilot is included in GitHub's independent third-party audit program, and the authoritative source for current attestations is the GitHub Copilot Trust Center, which centralizes transparency, compliance, and security documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  SOC 2
&lt;/h3&gt;

&lt;p&gt;Copilot falls within GitHub's SOC 2 program. For most enterprise procurement, the SOC 2 Type II report is the artifact your security and compliance teams will request. Obtain the current report through official channels, map its controls to your own control framework, and note the boundary of what is covered.&lt;/p&gt;

&lt;h3&gt;
  
  
  ISO
&lt;/h3&gt;

&lt;p&gt;GitHub maintains ISO/IEC certifications relevant to information security management. As with SOC 2, treat the certificate as evidence of a managed control environment, verify its current scope and validity, and confirm that the certified scope includes the Copilot surfaces you intend to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  GDPR
&lt;/h3&gt;

&lt;p&gt;For organizations subject to GDPR, the relevant levers are the Data Processing Agreement, the GitHub Privacy Statement, telemetry and metadata handling, and data residency. GitHub Enterprise Cloud with data residency lets an organization choose the geographic region where its data is stored and processed; by 2026 this scope extends to Copilot usage metrics and telemetry as well as code and metadata. Supported residency regions expanded through 2025 and 2026 to include EU, US, Australia, and Japan, with more planned. Note an important boundary: model inference for some providers may still be processed outside your residency region depending on the provider's infrastructure, so have your legal team validate the exact scope of residency commitments in your agreement.&lt;/p&gt;

&lt;h3&gt;
  
  
  HIPAA
&lt;/h3&gt;

&lt;p&gt;Healthcare organizations handling protected health information carry the strictest obligations. The controlling principle is that PHI must never enter Copilot's prompt context. Achieve this through content exclusion on repositories and paths that touch PHI, strict repository permission boundaries, and secret and data scanning. Expect additional internal validation time for regulated healthcare rollouts, and require a Business Associate arrangement and legal review of GitHub's terms before any Copilot surface touches a system in scope. When in doubt, exclude.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Copyright Commitment
&lt;/h3&gt;

&lt;p&gt;For IP risk, GitHub's Copilot Copyright Commitment provides indemnification for organizational plans against certain third-party IP claims arising from suggestions, subject to conditions such as having the public-code matching filter enabled. This is a meaningful procurement advantage of Business and Enterprise over personal plans, and it is a reason to enable the duplication-detection filter rather than turn it off for convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  HimanshuAI August Sale — FLAT 95% OFF
&lt;/h2&gt;

&lt;p&gt;The HimanshuAI August Sale is now live.&lt;/p&gt;

&lt;p&gt;For a limited time, get FLAT 95% OFF on my complete collection of premium AI Engineering digital playbooks.&lt;/p&gt;

&lt;p&gt;New Bundles&lt;/p&gt;

&lt;p&gt;• GenAI Engineering Vault — 16 Books&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• THE BUNDLE — LLM &amp;amp; Generative AI Testing Pro&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Coding Agents Mastery — Volume 1&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Ollama &amp;amp; Local LLMs — Complete 4 Book Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AWS Cloud Tester Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Salesforce Automation Testing Mastery Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Playwright + TypeScript Mastery Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coupon Code&lt;/p&gt;

&lt;p&gt;AI95&lt;/p&gt;

&lt;p&gt;Flat 95% OFF&lt;/p&gt;

&lt;p&gt;Explore&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Enterprise Governance
&lt;/h2&gt;

&lt;p&gt;Governance is the operating system for a Copilot deployment. It is the difference between a pilot that quietly sprawls into shadow AI and a program that finance, security, and engineering leadership can all defend.&lt;/p&gt;
&lt;h3&gt;
  
  
  Administration
&lt;/h3&gt;

&lt;p&gt;Enterprise and organization owners administer Copilot through GitHub's admin surfaces. The core responsibilities are enabling and disabling surfaces, managing the permitted model catalog, configuring content exclusion, setting the public-code duplication filter, and controlling the coding agent and code review. In 2026, managed settings can also be deployed to clients through mobile device management, so that VS Code and CLI configurations are enforced on managed devices rather than left to individual choice.&lt;/p&gt;
&lt;h3&gt;
  
  
  Seat Management
&lt;/h3&gt;

&lt;p&gt;Seats are assigned to users and teams. Enterprise owners can assign Copilot Enterprise or Business to organizations, or Business directly to users and teams. Because seats carry cost and AI-credit allowances, seat management is a continuous discipline: reclaim seats from inactive users, align assignment with cost centers, and use the usage analytics data to identify who is actually deriving value.&lt;/p&gt;
&lt;h3&gt;
  
  
  Policy Management
&lt;/h3&gt;

&lt;p&gt;Policy is where governance becomes enforcement, layered from enterprise down to organization. Define a default-deny posture for the highest-risk capabilities and grant them per-team as readiness is demonstrated. Version your policy decisions in a document that maps each policy to the risk it mitigates, because auditors will ask why each control is set the way it is.&lt;/p&gt;
&lt;h3&gt;
  
  
  Organization Settings
&lt;/h3&gt;

&lt;p&gt;Organization settings tie Copilot to identity and access. Enforce SSO, tie Copilot access to corporate identity, and ensure repository visibility and team boundaries reflect real need-to-know, since grounded Copilot surfaces inherit those boundaries.&lt;/p&gt;
&lt;h3&gt;
  
  
  Usage Analytics
&lt;/h3&gt;

&lt;p&gt;The usage and metrics APIs expose adoption and engagement signals: active users, acceptance behavior, surface usage, and, in 2026, adoption phases and time-to-adoption metrics, plus per-user budgets for cost centers in the billing UI. Use these to run the rollout as a measured program rather than a hopeful one. The questions worth answering monthly: which teams have adopted, which have stalled, where AI-credit consumption concentrates, and whether adoption correlates with the outcomes you care about, such as cycle time and review throughput. Instrument outcomes, not just activity, and resist vanity metrics that count suggestions without measuring whether they improved delivery.&lt;/p&gt;
&lt;h2&gt;
  
  
  Developer Productivity
&lt;/h2&gt;

&lt;p&gt;The productivity value of Copilot is realized across the whole engineering surface, not just greenfield feature code. The examples below are illustrative of how to prompt for production-quality output and what disciplined generation looks like across languages and stacks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Code Generation
&lt;/h3&gt;

&lt;p&gt;The pattern that produces good generation is a precise specification. In Java, a request to generate a resilient service method with explicit constraints yields code you can actually ship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate a method that fetches an Account by id with:&lt;/span&gt;
&lt;span class="c1"&gt;// - Constructor-injected AccountRepository and MeterRegistry&lt;/span&gt;
&lt;span class="c1"&gt;// - A Resilience4j @CircuitBreaker named "accountService" with fallback&lt;/span&gt;
&lt;span class="c1"&gt;// - Micrometer timer "account.fetch"&lt;/span&gt;
&lt;span class="c1"&gt;// - Throws AccountNotFoundException (unchecked) when absent&lt;/span&gt;
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AccountRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Timer&lt;/span&gt; &lt;span class="n"&gt;fetchTimer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AccountService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccountRepository&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MeterRegistry&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fetchTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"account.fetch"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@CircuitBreaker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"accountService"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fallbackMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"fetchFallback"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fetchTimer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;record&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccountNotFoundException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="nf"&gt;fetchFallback&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Throwable&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AccountServiceUnavailableException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Refactoring
&lt;/h3&gt;

&lt;p&gt;Refactoring is where grounded chat shines because it can reason about existing structure. A productive workflow is to select a method, open chat, and ask for a specific transformation with named constraints: extract a pure function, replace a nested conditional with a guard-clause structure, convert a callback chain to async/await, or introduce a strategy pattern to remove a switch. Always ask for behavior-preserving changes and generate tests before accepting the refactor, so the diff is verifiable rather than merely plausible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation
&lt;/h3&gt;

&lt;p&gt;Copilot generates docstrings, README sections, and API reference from code. In Python, a precise prompt produces useful docstrings rather than restated signatures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile_ledger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LedgerEntry&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ReconResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Reconcile a batch of ledger entries against expected balances.

    Groups entries by account, sums debits and credits, and flags any
    account whose net movement deviates from its expected balance by more
    than `tolerance`. Deterministic and side-effect free.

    Args:
        entries: Ledger entries to reconcile. May span multiple accounts.
        tolerance: Absolute currency amount below which a discrepancy is
            treated as matched. Must be non-negative.

    Returns:
        ReconResult with matched accounts and a list of discrepancies,
        each carrying the account id, expected and actual balances, and delta.

    Raises:
        ValueError: If tolerance is negative.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Unit Tests and Integration Tests
&lt;/h3&gt;

&lt;p&gt;Test generation is one of the highest-return uses. Ask for tests that cover the behavior you actually care about: boundaries, error paths, and invariants, not just the happy path. In TypeScript with a modern test runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate tests for parseRetryAfter(header: string | null): number | null&lt;/span&gt;
&lt;span class="c1"&gt;// Cover: numeric seconds, HTTP-date format, null, empty, malformed, negative.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseRetryAfter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../src/http/retry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parses integer seconds&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;120&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parses an HTTP-date into seconds from now&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUTCString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;future&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeLessThanOrEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns null for null, empty, or malformed input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;soon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;treats negative seconds as null&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseRetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API Development
&lt;/h3&gt;

&lt;p&gt;For API work, Copilot accelerates handler scaffolding, validation, and contract adherence. In C# with minimal APIs, a constrained prompt produces validated endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// POST /v1/transfers : validate request, enforce idempotency via&lt;/span&gt;
&lt;span class="c1"&gt;// Idempotency-Key header, return 201 with Location, 409 on duplicate key.&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/transfers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;TransferRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;FromHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Idempotency-Key"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ITransferService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IValidator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TransferRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidationProblem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDictionary&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;TransferStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Created&lt;/span&gt;   &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"/v1/transfers/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;TransferStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duplicate&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"duplicate idempotency key"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;                        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Problem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transfer failed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SQL Generation
&lt;/h3&gt;

&lt;p&gt;Copilot is effective at generating SQL from a described intent, and it is your responsibility to review the query plan implications. A prompt like "monthly active accounts by product for the last 12 months, excluding internal test accounts" yields a query you then validate against indexes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;monthly_active_accounts&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;account_events&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt;   &lt;span class="n"&gt;accounts&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'month'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'12 months'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt;  &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_internal_test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always confirm that supporting indexes exist (here, on &lt;code&gt;account_events(event_time, account_id)&lt;/code&gt; and &lt;code&gt;accounts(account_id)&lt;/code&gt;), because a generated query that is correct can still be a production incident if it triggers a sequential scan on a large table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure as Code
&lt;/h3&gt;

&lt;p&gt;Copilot is strong across IaC because these languages are pattern-dense and well represented. The discipline is identical to application code: specify constraints, then review for security and cost.&lt;/p&gt;

&lt;p&gt;Terraform with least-privilege intent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# S3 bucket for audit logs: versioned, SSE-KMS, public access blocked,&lt;/span&gt;
&lt;span class="c1"&gt;# 400-day lifecycle to Glacier, TLS-only bucket policy.&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"acme-audit-logs-${var.environment}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_versioning"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;versioning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enabled"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_server_side_encryption_configuration"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;apply_server_side_encryption_by_default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sse_algorithm&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:kms"&lt;/span&gt;
      &lt;span class="nx"&gt;kms_master_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_kms_key_arn&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;bucket_key_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket_public_access_block"&lt;/span&gt; &lt;span class="s2"&gt;"audit_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_s3_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audit_logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_acls&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;block_public_policy&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;ignore_public_acls&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;restrict_public_buckets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker with a multi-stage, non-root build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Multi-stage build for a Go service, distroless runtime, non-root user.&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.23&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux go build &lt;span class="nt"&gt;-trimpath&lt;/span&gt; &lt;span class="nt"&gt;-ldflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-s -w"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /out/app ./cmd/server

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/static:nonroot&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /out/app /app&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; nonroot:nonroot&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/app"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kubernetes with production-grade defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-api&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;payments-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;payments-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;payments-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;seccompProfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;RuntimeDefault&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payments-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.acme.io/payments-api:1.8.2&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;    &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/healthz&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
          &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/livez&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8080&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="na"&gt;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For CloudFormation and Azure Bicep the workflow is the same: describe the resource with its security and lifecycle constraints, generate, then review against your organization's guardrails and policy-as-code. Copilot removes the boilerplate; it does not remove your responsibility to enforce encryption, least privilege, network isolation, and tagging.&lt;/p&gt;

&lt;p&gt;The through-line across all of productivity: Copilot is fastest at the parts of engineering that are pattern-dense and slowest to add value where the work requires judgment about your specific system. Aim it at the boilerplate, keep humans on the judgment, and gate everything through review.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Assisted Testing
&lt;/h2&gt;

&lt;p&gt;Testing is arguably where AI assistance produces the most durable enterprise value, because tests are pattern-heavy, tedious to write comprehensively, and directly protect against the risk that AI-generated production code is wrong. The rule is to let Copilot draft breadth while humans own the assertions that encode real requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  JUnit and TestNG
&lt;/h3&gt;

&lt;p&gt;For JVM services, Copilot drafts parameterized tests, boundary cases, and exception paths. Ask explicitly for the cases that matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ParameterizedTest&lt;/span&gt;
&lt;span class="nd"&gt;@CsvSource&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;
    &lt;span class="s"&gt;"0,      0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"100,    5"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"999,    49"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"1000,   50"&lt;/span&gt;   &lt;span class="c1"&gt;// cap applies at and above 1000&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;computesRewardPointsWithCap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;spendCents&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;expectedPoints&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rewards&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pointsFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spendCents&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedPoints&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;rejectsNegativeSpend&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;assertThatThrownBy&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rewards&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pointsFor&lt;/span&gt;&lt;span class="o"&gt;(-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isInstanceOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IllegalArgumentException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TestNG follows the same approach with &lt;code&gt;@DataProvider&lt;/code&gt; for data-driven suites; ask Copilot to generate the provider and the test method together.&lt;/p&gt;

&lt;h3&gt;
  
  
  PyTest
&lt;/h3&gt;

&lt;p&gt;In Python, Copilot generates fixtures, parametrization, and property-style cases. A strong prompt names the invariants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;billing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;prorate&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;full,days_used,total_days,expected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_proration_is_linear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days_used&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;prorate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days_used&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_proration_rejects_impossible_usage&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;prorate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  End-to-End: Playwright, Cypress, Selenium
&lt;/h3&gt;

&lt;p&gt;For browser and end-to-end testing, Copilot scaffolds resilient interaction and assertion code. Playwright with TypeScript, using role-based locators and web-first assertions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user can transfer funds and sees confirmation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/transfers/new&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;From account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;selectOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;checking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Amount&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;250.00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Review transfer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Confirm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toHaveText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/transfer complete/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;transfers&lt;/span&gt;&lt;span class="se"&gt;\/[&lt;/span&gt;&lt;span class="sr"&gt;a-z0-9-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cypress and Selenium follow the same pattern of describing the user journey and the observable outcome; ask Copilot to prefer stable, semantic selectors over brittle CSS paths, and to assert on user-visible state rather than implementation details.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Testing: REST Assured
&lt;/h3&gt;

&lt;p&gt;For contract and API-level tests in the JVM ecosystem, REST Assured tests are quick to draft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;createTransferReturns201WithLocation&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;given&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Idempotency-Key"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"checking"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"to"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"savings"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"amountCents"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25000&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/transfers"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Location"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;matchesPattern&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v1/transfers/[a-f0-9-]+"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;equalTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"created"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The testing discipline that keeps AI-assisted tests honest: never accept a generated test whose assertions you have not read and understood. A test that passes but asserts the wrong thing is worse than no test, because it manufactures false confidence. Copilot writes the scaffolding; the engineer owns the meaning of "correct."&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Code Reviews
&lt;/h2&gt;

&lt;p&gt;Copilot's code review capability moved from novelty to infrastructure. It reached general availability and was re-architected to combine large language model reasoning with deterministic engines such as linters and CodeQL, so that review output blends contextual judgment with rule-based rigor rather than relying on the model alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug Detection and Code Smells
&lt;/h3&gt;

&lt;p&gt;Copilot code review reads a pull request's diff with surrounding project context and surfaces likely defects, risky patterns, and code smells at the line level, with suggested edits you can apply. It is effective at catching the classes of mistakes that reviewers routinely miss under time pressure: off-by-one errors, null-handling gaps, resource leaks, inconsistent error handling, and violations of local conventions. Treat its comments as a fast first pass that raises the floor, not a replacement for human review that owns the ceiling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Vulnerability Detection and Static Analysis
&lt;/h3&gt;

&lt;p&gt;There are two complementary automated mechanisms, and conflating them causes gaps. Code review suggestions handle general quality and best-practice improvements that a developer applies explicitly. Copilot Autofix for code scanning targets security specifically: it proposes fixes for code-scanning alerts, integrates with CodeQL, and helps remediate and prevent vulnerabilities. GitHub is explicit that Autofix will not generate a fix for every alert in every situation, so it is an accelerant for remediation, not a guarantee of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Debt Reduction
&lt;/h3&gt;

&lt;p&gt;Used deliberately, AI review compounds into debt reduction. Point Copilot chat at a module and ask where the risk concentrates: functions with high cyclomatic complexity, duplicated logic, missing tests, or fragile error handling. Then use the coding agent or agent mode to execute targeted, test-covered refactors. The winning pattern is small, verifiable increments gated through review, not sweeping rewrites.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coverage at Organizational Scale
&lt;/h3&gt;

&lt;p&gt;In 2026, organizations can enable Copilot code review on all pull requests, including PRs from contributors who are not assigned a Copilot seat, with that usage billed to the organization as AI credits. This gives complete review coverage across a repository without buying a seat for every occasional contributor. It is off by default and admin-controlled, and code-review workflows consume both AI credits and, as of mid-2026, Actions minutes, so factor the cost into capacity planning before enabling it fleet-wide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot Chat Deep Dive
&lt;/h2&gt;

&lt;p&gt;Chat is the surface most developers touch most often, and using it well is a skill worth teaching explicitly. The following are the canonical, high-value chat workflows.&lt;/p&gt;

&lt;p&gt;Explain code: ask for the responsibilities of a module, the flow of a request, the invariants a function assumes, or the blast radius of a change. On Enterprise this is grounded in your real code, which makes it the fastest path through unfamiliar systems.&lt;/p&gt;

&lt;p&gt;Generate code: supply intent, constraints, and output shape as shown earlier; the specificity of constraints determines quality far more than model choice.&lt;/p&gt;

&lt;p&gt;Refactor: request behavior-preserving transformations by name and generate tests before accepting.&lt;/p&gt;

&lt;p&gt;Debug: paste the failing test or stack trace, describe expected versus actual behavior, and ask for hypotheses ranked by likelihood with the diagnostic step for each, rather than asking for a fix blindly.&lt;/p&gt;

&lt;p&gt;Optimize: describe the hot path and the constraint you are optimizing for (latency, allocation, query count), and ask for options with their trade-offs; never accept a performance change without a measurement.&lt;/p&gt;

&lt;p&gt;Document: generate docstrings, API references, and change summaries; then verify accuracy, because documentation that confidently states something false is a liability.&lt;/p&gt;

&lt;p&gt;Generate unit tests and API tests: ask for boundaries, error paths, and invariants explicitly, and read every assertion.&lt;/p&gt;

&lt;p&gt;Generate SQL and regex: describe the intent and the data; for regex especially, always ask for a set of positive and negative test strings so you can verify the pattern rather than trusting it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Prompt: regex to validate an internal ticket id of the form ACME-&amp;lt;digits&amp;gt;,
# 4 to 8 digits, case-insensitive prefix. Provide test strings.
^(?i)ACME-\d{4,8}$

# Should match:   ACME-1234, acme-12345678
# Should not match: ACME-123, ACME-123456789, XACME-1234, ACME-12a4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meta-skill across all of these is verification. Chat gives you a fast draft and a second opinion; it does not give you the authority to skip understanding. The engineers who get the most from chat are the ones who treat every answer as a hypothesis to confirm, not a conclusion to paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Workflows
&lt;/h2&gt;

&lt;p&gt;Copilot's value shifts by role, and a mature enterprise tailors enablement to each.&lt;/p&gt;

&lt;p&gt;Backend development benefits from generation of handlers, validation, persistence, and resilience patterns, with grounded chat answering questions about service boundaries and shared libraries. Frontend development gains from component scaffolding, state-management wiring, accessibility-aware markup, and test generation. Microservices work leans on Copilot for consistent cross-cutting concerns: the same logging, tracing, error, and retry patterns applied uniformly across services, ideally encoded in instruction files so every repository generates to the same standard.&lt;/p&gt;

&lt;p&gt;Cloud development uses Copilot heavily for IaC, SDK calls, and provider-specific glue, always reviewed against security guardrails. DevOps and automation teams get pipeline configuration, scripting, and toolchain integration; the CLI agent is especially valuable here. SRE work uses grounded chat for rapid comprehension during incidents, runbook generation, and postmortem drafting, with the caveat that nothing generated is trusted without verification against real telemetry. Data engineering benefits from SQL and transformation generation, schema reasoning, and pipeline scaffolding, with mandatory review of query plans and data-handling for anything touching sensitive datasets.&lt;/p&gt;

&lt;p&gt;The organizing principle is that Copilot amplifies an existing workflow; it does not define one. Teams with strong conventions, good tests, and clear ownership get a large multiplier. Teams without those get faster production of unreviewed, inconsistent code. Enablement should therefore invest in the surrounding discipline as much as in the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Integration
&lt;/h2&gt;

&lt;p&gt;Copilot lives inside an existing toolchain, and its enterprise value depends on integrating cleanly with CI/CD and DevSecOps rather than bypassing it. The controlling idea: AI-generated code enters the same pipeline as human code and must pass the same gates.&lt;/p&gt;

&lt;p&gt;A representative GitHub Actions workflow that treats a Copilot-authored pull request exactly like any other, gating on build, tests, static analysis, security scanning, and code scanning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ci&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npm"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test -- --coverage&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SonarQube quality gate&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonarsource/sonarqube-scan-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SONAR_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SONAR_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Snyk dependency scan&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snyk/actions/node@master&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SNYK_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SNYK_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--severity-threshold=high&lt;/span&gt;

  &lt;span class="na"&gt;codeql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/init@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;languages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;javascript-typescript"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/analyze@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The integration points that matter for an enterprise Copilot program:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions runs the gates that every AI-authored PR must clear. Branch protection and required checks are the enforcement layer for agentic surfaces.&lt;/li&gt;
&lt;li&gt;CodeQL provides semantic security analysis and is the engine behind Autofix; keep it in required checks.&lt;/li&gt;
&lt;li&gt;Snyk and Dependabot manage dependency and supply-chain risk, which matters more when generation can introduce unfamiliar dependencies.&lt;/li&gt;
&lt;li&gt;SonarQube enforces quality gates and tracks the maintainability of generated code over time.&lt;/li&gt;
&lt;li&gt;Azure DevOps, Jenkins, and GitLab shops integrate the same principle: Copilot-authored changes flow through existing pipelines. Copilot code review is also extending into Azure Repos, and the coding agent can be assigned work from issue trackers, but the merge gate remains human review plus automated checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The anti-pattern to forbid explicitly: any path that lets AI-generated code reach production without passing the same automated and human gates as human-written code. If the coding agent can open a PR, that PR must face branch protection, required checks, and code-owner review. Governance of agentic engineering is, in practice, the discipline of never weakening the merge gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt Engineering Best Practices and Enterprise Prompt Library
&lt;/h2&gt;

&lt;p&gt;At enterprise scale, prompting stops being an individual art and becomes shared infrastructure. Teams that standardize prompts get consistent output; teams that do not get as many styles as they have engineers.&lt;/p&gt;

&lt;p&gt;Best practices that generalize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lead with intent, then constraints, then output shape. Specificity of constraints is the primary quality lever.&lt;/li&gt;
&lt;li&gt;Reference concrete symbols, files, and conventions rather than describing them vaguely.&lt;/li&gt;
&lt;li&gt;Encode standing conventions in repository instruction files so they need not be repeated per prompt.&lt;/li&gt;
&lt;li&gt;Chain complex work into reviewable steps rather than requesting whole features at once.&lt;/li&gt;
&lt;li&gt;Ask for trade-offs and alternatives on anything involving judgment, so you review a decision rather than accept a default.&lt;/li&gt;
&lt;li&gt;Always request tests and verification artifacts alongside generated logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An enterprise prompt library is a version-controlled set of reusable templates, treated like any other shared asset. Representative templates:&lt;/p&gt;

&lt;p&gt;Refactor template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refactor {selection} to {goal, e.g. "extract pure functions and remove the nested conditional"}.
Constraints: preserve behavior and the public signature; use {approved libraries};
follow {repo conventions from copilot-instructions.md}. Generate tests that prove
behavior is unchanged before and after. Return the diff and the tests.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secure-endpoint template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement {HTTP method + path}. Validate input with {validation library}; enforce
{authn/authz requirement}; add structured logging with a correlation id; handle errors
with {error model}; never log secrets or PII. Return the handler plus unit and API tests
covering success, validation failure, and authorization failure.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test-generation template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate {framework} tests for {unit}. Cover boundaries, error paths, and these invariants:
{list}. Prefer semantic assertions over implementation details. Do not assert on internal
private state. List any behavior you could not determine from the code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store these under version control, review changes to them, and measure which templates correlate with fewer review comments and lower defect escape. The prompt library becomes an organizational asset that encodes hard-won knowledge about what produces reliable output in your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Best Practices
&lt;/h2&gt;

&lt;p&gt;Secure use of Copilot is a superset of secure coding, because generation can both help and hurt security depending on discipline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secrets Management and Credential Detection
&lt;/h3&gt;

&lt;p&gt;Never let secrets enter prompt context, and never trust generated code to handle secrets correctly without review. Use content exclusion to keep secret-bearing files out of context, enable secret scanning with push protection so credentials cannot be committed, and review any generated code that touches authentication, tokens, or key material. Generated code will sometimes propose hardcoded placeholders; treat every literal that looks like a credential as a defect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure Coding and the OWASP Top 10
&lt;/h3&gt;

&lt;p&gt;AI-generated code can reproduce insecure patterns present in training data. Review generated code against the OWASP Top 10 with the same rigor you apply to human code: parameterized queries rather than string-concatenated SQL to prevent injection, output encoding to prevent cross-site scripting, correct authorization checks on every protected operation, safe deserialization, and validated redirects. Copilot can help here too: ask it to review a handler specifically for OWASP Top 10 issues, but treat its answer as a first pass that CodeQL and human review confirm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supply Chain Security
&lt;/h3&gt;

&lt;p&gt;Generation can introduce dependencies, and dependencies are the largest modern attack surface. Enforce that every new dependency is justified, scanned by Snyk or equivalent, tracked by Dependabot, and consistent with your approved-component policy. Prefer prompts that instruct Copilot to use existing internal libraries rather than pulling new packages, and make "no new dependency without review" an explicit constraint in security-sensitive templates.&lt;/p&gt;

&lt;p&gt;The overarching security posture: Copilot shifts some risk left (it can find issues earlier) and introduces some risk (it can generate insecure or license-contaminated code). Net security improves only when the surrounding controls, content exclusion, public-code filtering, secret scanning, CodeQL, dependency scanning, and human review, are all in place. Deployed without them, it is a faster way to ship the same mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Hallucinations, Limitations, and Responsible AI
&lt;/h2&gt;

&lt;p&gt;Copilot is a probabilistic system, and treating its output as authoritative is the root cause of most AI-related engineering incidents. A mature program builds explicit defenses against the model's failure modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Hallucinations
&lt;/h3&gt;

&lt;p&gt;Hallucination in a coding context takes specific forms: inventing an API method that does not exist, referencing a library function with the wrong signature, confidently describing behavior that the code does not have, or fabricating a plausible but incorrect explanation of why a bug occurs. These failures are dangerous precisely because they are fluent. The defense is verification against ground truth: run the code, check the API against real documentation, and confirm explanations against actual behavior. Grounding on Enterprise reduces but does not eliminate hallucination, because retrieval can surface the wrong context or miss relevant context entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;Copilot's limitations are structural, not temporary bugs. It has a finite context window, so it cannot reason about arbitrarily large changes at once. It lacks true understanding of your system's runtime behavior, business constraints, and organizational context beyond what enters its context. It reflects patterns in training data, including outdated or insecure ones. And it optimizes for plausible continuation, not correctness. Knowing these limits tells you where to keep humans firmly in control: architectural decisions, security-critical logic, anything with regulatory implications, and anything where being confidently wrong is expensive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation Strategies
&lt;/h3&gt;

&lt;p&gt;Validation is layered. Automated gates (tests, static analysis, security scanning) catch mechanical errors. Human review catches judgment errors and context the model lacks. For anything generated, the minimum bar is that it passes the same automated gates as human code and is read and understood by a competent human who takes ownership. High-risk changes warrant additional validation: threat modeling for security-relevant code, load testing for performance-sensitive paths, and domain-expert review for regulated logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human Review and Code Ownership
&lt;/h3&gt;

&lt;p&gt;The accountability principle is non-negotiable: a human owns every line that ships, regardless of who or what generated it. "The AI wrote it" is not a defense in an incident review, a security audit, or a compliance finding. Code ownership through CODEOWNERS, required reviews, and clear team boundaries must apply identically to AI-authored and human-authored changes. The engineer who approves a pull request owns its contents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsible AI
&lt;/h3&gt;

&lt;p&gt;Responsible use in an enterprise means transparency about where AI is used, controls proportional to risk, respect for the data-handling and IP commitments of your plan, and a culture that treats AI as an accelerant for accountable humans rather than a replacement for accountability. It also means honesty about limits with your own organization: overselling AI capability sets up failures that damage trust in an otherwise valuable program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Optimization
&lt;/h2&gt;

&lt;p&gt;Getting the best results from Copilot at scale is partly a performance-engineering problem.&lt;/p&gt;

&lt;p&gt;For large repositories, the constraint is context relevance. Well-factored code with clear module boundaries produces better retrieval and better suggestions than a tangled monolith. Investing in code organization pays off directly in AI-assisted productivity, which is a genuinely new incentive for good architecture. Ensure the repositories that matter are indexed on Enterprise, and use curated context spaces to focus grounding on the relevant domain.&lt;/p&gt;

&lt;p&gt;For context management, curate what enters the window: keep relevant files open, close noise, reference symbols explicitly, and use instruction files for standing conventions. The quality of a suggestion is usually a function of the quality of assembled context, so context hygiene is the highest-leverage habit.&lt;/p&gt;

&lt;p&gt;For response quality, choose the model that fits the task. Lightweight models are fast and cheap for routine completion and simple chat; frontier models earn their higher cost on hard, multi-file, or architecturally sensitive work. Organization policy should make the appropriate models available and, where useful, guide teams toward the right tool for each job. Measure the trade-off: latency and AI-credit consumption against output quality, and tune the permitted model set accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Enterprise Use Cases
&lt;/h2&gt;

&lt;p&gt;The patterns below reflect how large organizations actually deploy Copilot, with the governance that makes each defensible.&lt;/p&gt;

&lt;p&gt;Large banking systems use Business or Enterprise with strict content exclusion on core-banking and payment repositories, a restricted model catalog approved by legal, mandatory human review and CODEOWNERS on every change, and data residency configured to their regulatory region. The high-value workflows are test generation, legacy comprehension, and consistent implementation of cross-cutting concerns, with agentic surfaces enabled only on lower-risk services first.&lt;/p&gt;

&lt;p&gt;Healthcare organizations enforce the absolute rule that PHI never enters prompt context, achieved through content exclusion and repository permissions, plus a Business Associate arrangement and legal review. They typically see longer validation cycles and deploy Copilot first on non-clinical systems (internal tooling, data platforms) before regulated ones.&lt;/p&gt;

&lt;p&gt;Retail and e-commerce use Copilot broadly across frontend, backend, and data engineering, with emphasis on test generation and rapid iteration, gated through standard CI/CD. Insurance and telecommunications apply patterns similar to banking, with strong governance on rating engines, billing, and customer-data systems.&lt;/p&gt;

&lt;p&gt;Government deployments require the strictest posture: approved-region data residency, compliance-appropriate model hosting, comprehensive audit, and conservative enablement of agentic capabilities. Manufacturing and industrial software use Copilot across embedded-adjacent and cloud systems, with careful review of anything touching safety-relevant logic.&lt;/p&gt;

&lt;p&gt;Enterprise SaaS and developer-platform companies are often the most aggressive adopters, using the coding agent for issue-to-PR workflows on well-tested services, heavy AI-assisted testing, and grounded chat for onboarding into large codebases. Migration and legacy-modernization projects are a standout use case: grounded comprehension of undocumented legacy systems, incremental test-covered refactoring, and translation of patterns across languages during cloud transformation. In every case the constant is that governance scales with the sensitivity of the system, and agentic autonomy is granted last to the systems where being wrong is most expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Problems
&lt;/h2&gt;

&lt;p&gt;The failure modes below are predictable, which means they are preventable.&lt;/p&gt;

&lt;p&gt;AI hallucinations produce confident, wrong output; the fix is mandatory verification and never trusting fluency as correctness. Wrong code generation slips through when review is weak; the fix is the same gates as human code plus a culture that reads generated diffs carefully. Security risks arise from insecure generated patterns and license contamination; the fix is content exclusion, public-code filtering, CodeQL, secret scanning, and dependency scanning together. Prompt ambiguity produces inconsistent output; the fix is a prompt library, instruction files, and specificity discipline.&lt;/p&gt;

&lt;p&gt;Repository scale degrades suggestion quality; the fix is code organization, indexing, and curated context. Context limitations cause the model to miss relevant code; the fix is explicit context management and grounding. Poor code quality accumulates when generation outpaces review; the fix is quality gates and treating maintainability as a first-class metric. Over-reliance on AI erodes engineer skill and ownership; the fix is a culture that positions AI as an accelerant for accountable humans, plus review practices that require understanding.&lt;/p&gt;

&lt;p&gt;Governance failures and compliance issues stem from ungoverned personal-plan use, missing content exclusion, and unclear policy; the fix is standardizing on organizational plans, default-deny policy for high-risk surfaces, and documented control mapping. Developer adoption resistance is common and legitimate; the fix is enablement that respects engineers' judgment, transparency about limits, and demonstrating value on real workflows rather than mandating usage. The meta-lesson is that nearly every Copilot problem is a governance or discipline problem wearing a technology costume.&lt;/p&gt;

&lt;h2&gt;
  
  
  HimanshuAI August Sale — FLAT 95% OFF
&lt;/h2&gt;

&lt;p&gt;The HimanshuAI August Sale is now live.&lt;/p&gt;

&lt;p&gt;For a limited time, get FLAT 95% OFF on my complete collection of premium AI Engineering digital playbooks.&lt;/p&gt;

&lt;p&gt;New Bundles&lt;/p&gt;

&lt;p&gt;• GenAI Engineering Vault — 16 Books&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• THE BUNDLE — LLM &amp;amp; Generative AI Testing Pro&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/THEBUNDLE-LLMGenerativeAITestingPro&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Coding Agents Mastery — Volume 1&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Bundle-AICodingAgentsMastery-Volume1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Ollama &amp;amp; Local LLMs — Complete 4 Book Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AWS Cloud Tester Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AWS-Cloud-Tester-3-Books-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• Salesforce Automation Testing Mastery Series&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;• AI Playwright + TypeScript Mastery Bundle&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/The-Complete-AI-Playwright-TypeScript-Mastery-Bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coupon Code&lt;/p&gt;

&lt;p&gt;AI95&lt;/p&gt;

&lt;p&gt;Flat 95% OFF&lt;/p&gt;

&lt;p&gt;Explore&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Future Roadmap
&lt;/h2&gt;

&lt;p&gt;The trajectory is clear even if specific dates are not. The center of gravity is moving from assistance to agency.&lt;/p&gt;

&lt;p&gt;AI coding agents are becoming first-class participants in the SDLC, taking issues to pull requests, running in isolated environments, and operating across the IDE, the command line, GitHub.com, and mobile. Autonomous development will expand from well-scoped, well-tested services toward more of the codebase as trust and tooling mature, but the merge gate, human review plus automated checks, remains the durable control point.&lt;/p&gt;

&lt;p&gt;Agent orchestration is consolidating. The direction is a single control plane that runs agents from multiple vendors under one subscription, one audit surface, and one policy layer, positioning Copilot as the interface for coding agents generally rather than a single-vendor tool. Agentic engineering as a discipline is emerging: engineers increasingly specify intent, review generated work, and own outcomes, spending less time producing every line and more time on architecture, verification, and judgment. AI pair programming will continue to blur the line between suggestion and execution, with the human role shifting toward direction and accountability.&lt;/p&gt;

&lt;p&gt;For platform teams the strategic implication is to build the governance, review, and measurement infrastructure now, because the capabilities will keep advancing and the organizations that thrive will be the ones whose guardrails scaled ahead of the autonomy. Treat specific product names and dates as a snapshot and track the official changelog, because this surface changes monthly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices, Anti-Patterns, and Enterprise Checklist
&lt;/h2&gt;

&lt;p&gt;Best practices, distilled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standardize on Business or Enterprise; forbid personal plans for proprietary code.&lt;/li&gt;
&lt;li&gt;Configure content exclusion, public-code filtering, and a legally approved model catalog as a baseline.&lt;/li&gt;
&lt;li&gt;Apply the same CI/CD and human-review gates to AI-authored and human-authored code, without exception.&lt;/li&gt;
&lt;li&gt;Encode conventions in version-controlled instruction files and a shared prompt library.&lt;/li&gt;
&lt;li&gt;Enable agentic surfaces progressively, lowest-risk systems first, with full branch protection.&lt;/li&gt;
&lt;li&gt;Instrument outcomes, not just activity, and run the rollout as a measured program.&lt;/li&gt;
&lt;li&gt;Keep a human owning every line that ships.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anti-patterns to forbid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any path that lets generated code bypass the merge gate.&lt;/li&gt;
&lt;li&gt;Trusting fluent output without verification.&lt;/li&gt;
&lt;li&gt;Personal-plan use on corporate code.&lt;/li&gt;
&lt;li&gt;Turning off the public-code filter for convenience, forfeiting IP indemnity conditions.&lt;/li&gt;
&lt;li&gt;Measuring suggestion counts instead of delivery outcomes.&lt;/li&gt;
&lt;li&gt;Mandating adoption without enablement, which breeds resistance and shadow usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enterprise adoption checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plan selection and seat strategy documented, with AI-credit budgeting per cost center.&lt;/li&gt;
&lt;li&gt;SSO enforced and Copilot tied to corporate identity.&lt;/li&gt;
&lt;li&gt;Content exclusion configured for sensitive repositories and paths.&lt;/li&gt;
&lt;li&gt;Model catalog restricted to approved providers; public-code filter enabled.&lt;/li&gt;
&lt;li&gt;Data residency configured to the required region; DPA and Trust Center artifacts reviewed.&lt;/li&gt;
&lt;li&gt;Compliance mapping (SOC 2, ISO, GDPR, HIPAA as applicable) completed with legal.&lt;/li&gt;
&lt;li&gt;CI/CD gates (tests, CodeQL, Snyk, SonarQube, Dependabot) required on all PRs.&lt;/li&gt;
&lt;li&gt;Branch protection and CODEOWNERS enforced, including for coding-agent PRs.&lt;/li&gt;
&lt;li&gt;Instruction files and a prompt library established and version-controlled.&lt;/li&gt;
&lt;li&gt;Usage analytics and outcome metrics instrumented and reviewed regularly.&lt;/li&gt;
&lt;li&gt;Enablement and responsible-AI training delivered to engineers.&lt;/li&gt;
&lt;li&gt;A named owner for the Copilot program with authority over policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Interview Questions
&lt;/h2&gt;

&lt;p&gt;These questions target the depth expected of senior and staff engineers, platform leads, and security engineers evaluating or operating an enterprise Copilot program.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Why are personal Copilot plans unsuitable for enterprise proprietary code, specifically?&lt;/strong&gt;&lt;br&gt;
Because personal plans (Free, Pro, Pro+) default to allowing interaction data to be used for model improvement unless the user opts out, and they lack centralized policy control, content exclusion, and administrative governance. Business and Enterprise are contractually excluded from training on customer interaction data and provide the controls an enterprise needs. The distinction is contractual and structural, not a matter of settings a user might toggle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Explain the difference between IDE agent mode and the coding agent, and why it matters for governance.&lt;/strong&gt;&lt;br&gt;
Agent mode runs in the IDE, keeps the human in the loop on edits, and uses the model selected in chat. The coding agent is a hosted asynchronous service that takes an issue or task, works in an isolated environment, and opens a pull request. The governance difference is where the control gate sits: for agent mode it is the developer approving edits before commit; for the coding agent it is the pull request review with branch protection and required checks. Treating a coding-agent PR as anything less than a human PR breaks the accountability model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What is the practical significance of Copilot being a model marketplace rather than a single model?&lt;/strong&gt;&lt;br&gt;
It decouples the enterprise from a single vendor's model behavior while retaining one subscription, one audit surface, and one policy layer. Platform teams can permit models that fit their latency, cost, quality, and data-handling requirements, standardize completion models via admin policy, and let chat and agent surfaces pick per task. It also means data-handling terms vary by provider, so the permitted model list is a security decision, not just a quality one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. How does repository indexing enable codebase-aware chat, and what limits its effectiveness?&lt;/strong&gt;&lt;br&gt;
Indexing builds a semantic representation of the repository so Copilot can retrieve the most relevant files for a question and ground its answer in real code. Effectiveness is limited by code organization: well-factored code with clear boundaries retrieves better than a tangled monolith. Retrieval can also surface the wrong context or miss relevant context, so grounding reduces but does not eliminate hallucination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Content exclusion versus a &lt;code&gt;.copilotignore&lt;/code&gt; file: what is the difference and why does it matter?&lt;/strong&gt;&lt;br&gt;
Content exclusion is configured and enforced centrally through GitHub's admin surfaces, so it cannot be bypassed by an individual's editor settings. A client-side ignore file is not an officially enforced server-level control. For any real security requirement you must use org- or repo-level exclusion, because enforcement must not depend on user configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Walk through the data flow of an inline completion in an enterprise deployment.&lt;/strong&gt;&lt;br&gt;
Copilot assembles context from the active file, open editors, and neighboring symbols; applies content exclusions to remove restricted paths; runs filters; sends the request to the admin-configured completion model; and post-processes the response including a public-code matching filter. On Business and Enterprise the code sent is discarded after processing rather than retained for training. Understanding this flow explains that suggestion quality is mostly a function of assembled context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. How would you architect Copilot governance for a bank subject to strict data residency?&lt;/strong&gt;&lt;br&gt;
Standardize on Enterprise on GitHub Enterprise Cloud with data residency set to the required region; restrict the model catalog to legally approved providers, noting that some inference may occur outside the region depending on the provider; enforce content exclusion on core-banking and payment repositories; require human review and CODEOWNERS on every change; enable agentic surfaces only on lower-risk services first; and document control mapping for auditors. Validate the exact residency scope in the DPA with legal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Why is the public-code matching filter relevant to IP indemnity?&lt;/strong&gt;&lt;br&gt;
GitHub's Copilot Copyright Commitment provides indemnification for organizational plans subject to conditions, including having the duplication-detection filter enabled. Turning the filter off for convenience can forfeit those conditions. So the filter is both a technical control against license contamination and a prerequisite for the contractual IP protection that makes organizational plans attractive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. What are the two distinct automated fixing mechanisms in Copilot, and why not conflate them?&lt;/strong&gt;&lt;br&gt;
Code review suggestion implementation handles general quality and best-practice improvements that a developer applies explicitly. Copilot Autofix for code scanning targets security, integrates with CodeQL, and proposes fixes for scanning alerts, though it will not fix every alert. Conflating them creates a gap: assuming general review covers security leaves vulnerabilities unaddressed that only the scanning-plus-Autofix path handles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. How do you prevent AI-generated code from bypassing your quality and security gates?&lt;/strong&gt;&lt;br&gt;
Enforce branch protection with required status checks and required reviews on every branch that can reach production, and apply them identically to coding-agent PRs. Keep tests, CodeQL, dependency scanning, and quality gates as required checks. The rule is that no path exists for generated code to merge without passing the same gates as human code; governance of agentic engineering is, in practice, never weakening the merge gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. What telemetry does Copilot collect, and how do you handle it under GDPR?&lt;/strong&gt;&lt;br&gt;
It collects usage metrics, IDE interaction signals such as accept or dismiss, and performance data, distinct from code content. Under GDPR you review the Privacy Statement and DPA, configure data residency (which by 2026 extends to Copilot telemetry), document collection and retention in your impact assessment, and, where required, use managed telemetry export into your own observability stack with your own retention rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. How does prompt engineering inside an IDE differ from prompting a standalone chatbot?&lt;/strong&gt;&lt;br&gt;
The model already has editor context, so the job is supplying missing intent, constraints, and output shape precisely, and managing what enters the finite context window. Specificity of constraints drives quality more than eloquence. Standing conventions belong in instruction files rather than repeated prose, and complex work is chained into reviewable steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. Describe a responsible-AI review process for a security-critical change generated by Copilot.&lt;/strong&gt;&lt;br&gt;
Require that it passes the same automated gates as human code (tests, CodeQL, dependency scanning), then add threat modeling and domain-expert review proportional to risk, explicit OWASP Top 10 review, and secret handling verification. A named human owner approves and owns the change. The generated origin does not reduce scrutiny; if anything it raises it for security-critical paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. What is the significance of managed settings deployment via MDM in 2026?&lt;/strong&gt;&lt;br&gt;
It lets administrators enforce Copilot configuration on managed devices for the IDE and CLI rather than relying on individual choice, closing a governance gap where per-user settings could otherwise diverge from policy. It turns client configuration into an enforceable control, which matters for regulated environments that must demonstrate consistent controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. How should an enterprise budget for AI credits, and why is it separate from seats?&lt;/strong&gt;&lt;br&gt;
Seats cover unlimited everyday completions, while premium features (chat, agents, code review) meter against a monthly AI-credit allowance, with overage billed by usage. Consumption varies enormously by workflow: agent-heavy teams consume far more than completion-heavy teams. Budget credits per cost center, instrument consumption from day one, and use per-user budgets to prevent surprises, because a naive seat-only budget will misforecast cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;16. When is Copilot Enterprise worth the premium over Business?&lt;/strong&gt;&lt;br&gt;
When you can name specific, recurring workflows where answers grounded in your indexed repositories and internal knowledge save real time, and you are already on GitHub Enterprise Cloud. Both tiers offer the same governance and comparable coding quality; the premium buys codebase-aware chat, knowledge bases, PR summaries, Copilot in GitHub.com, and priority model access. Without a named grounded workflow, start on Business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. How do instruction files change team-level output consistency?&lt;/strong&gt;&lt;br&gt;
Repository, personal, and organization instruction files, plus agent-definition files, encode conventions, preferred libraries, and constraints that apply to every interaction, so generation converges on a shared standard rather than diverging per engineer. Because they live in version control and code review, they are auditable and improvable like any source of truth, which is what makes consistency scalable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What failure modes make AI-generated tests dangerous, and how do you mitigate them?&lt;/strong&gt;&lt;br&gt;
A generated test can pass while asserting the wrong thing, manufacturing false confidence. Mitigation is to read and understand every assertion, require tests to encode real requirements (boundaries, error paths, invariants), and prefer semantic assertions over implementation details. Generated scaffolding is welcome; unreviewed assertions are not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. How does Copilot respect access control, and what must you audit before enabling grounded surfaces?&lt;/strong&gt;&lt;br&gt;
Grounded answers and coding-agent actions are bounded by what the acting identity can access; Copilot faithfully surfaces anything a permitted identity can reach. Before enabling grounded chat or the coding agent broadly, audit repository permissions and team boundaries, because over-broad access becomes over-broad grounding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. Explain the role of CodeQL in an AI-assisted pipeline.&lt;/strong&gt;&lt;br&gt;
CodeQL provides semantic security analysis and is the engine behind Autofix. In an AI-assisted pipeline it is a required check that catches vulnerability classes that generation can introduce, and it feeds the Autofix workflow that proposes remediations. Keeping it in required checks ensures generated code faces the same semantic security bar as human code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21. What is the strategic meaning of an agent-orchestration control plane?&lt;/strong&gt;&lt;br&gt;
It consolidates agents from multiple vendors under one subscription, one audit surface, and one policy layer across IDE, CLI, GitHub.com, and mobile, positioning Copilot as the interface for coding agents generally. Strategically it reduces vendor lock-in at the model layer while centralizing governance, which is exactly what enterprises need as autonomy increases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22. How do you measure whether a Copilot rollout is succeeding?&lt;/strong&gt;&lt;br&gt;
Instrument outcomes, not just activity. Track adoption by team, AI-credit consumption concentration, and correlation with delivery outcomes such as cycle time, review throughput, and defect escape, using the usage and metrics APIs including adoption-phase data. Resist vanity metrics that count suggestions; the question is whether delivery improved without quality regressing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23. What are the structural limitations of Copilot that keep humans in control?&lt;/strong&gt;&lt;br&gt;
A finite context window, no true understanding of runtime behavior or business constraints beyond supplied context, reflection of patterns (including insecure or outdated ones) from training data, and optimization for plausible continuation rather than correctness. These limits map directly to where humans must own decisions: architecture, security-critical logic, regulated behavior, and anything where confident wrongness is costly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24. How would you roll out the coding agent responsibly across a large organization?&lt;/strong&gt;&lt;br&gt;
Enable it first on well-tested, lower-risk services with strong branch protection and CODEOWNERS; treat its PRs identically to human PRs through all gates; measure defect escape and review burden; and expand to higher-risk systems only as trust and tooling mature. Keep it off by default at the org level and grant per-team as readiness is demonstrated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25. A developer says "the AI wrote it" during an incident review. How do you respond, and what does it reveal?&lt;/strong&gt;&lt;br&gt;
The response is that the origin is irrelevant to accountability: a human approved and owns the code that shipped. It reveals a governance gap, either in review discipline or in culture, because ownership must attach to the approver regardless of authorship. The fix is CODEOWNERS, required review, and a culture that treats AI as an accelerant for accountable humans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26. How do supply-chain risks change when generation can introduce dependencies?&lt;/strong&gt;&lt;br&gt;
Generation can pull unfamiliar packages, expanding the largest modern attack surface. Enforce that every new dependency is justified, scanned, tracked by Dependabot, and consistent with approved-component policy, and prefer prompts that instruct Copilot to reuse internal libraries. Make "no new dependency without review" an explicit constraint in security-sensitive prompt templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Does GitHub Copilot train on my enterprise code?&lt;/strong&gt;&lt;br&gt;
No. On Copilot Business and Enterprise, your prompts and suggestions are not used for model training, and code sent for completions is discarded after processing. This is a contractual commitment. Personal plans (Free, Pro, Pro+) default to allowing interaction data for model improvement unless the user opts out, which is why personal plans are unsuitable for proprietary work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. What is the difference between Copilot Business and Copilot Enterprise?&lt;/strong&gt;&lt;br&gt;
Both offer governance, IP indemnity, and privacy commitments with comparable coding quality. Enterprise adds codebase-aware chat grounded in your indexed repositories, knowledge bases, PR summaries, Copilot inside GitHub.com, priority model access, and a larger AI-credit allowance, and requires GitHub Enterprise Cloud. Choose Enterprise when grounded, codebase-aware answers produce recurring, nameable value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Can I control which AI models my organization uses?&lt;/strong&gt;&lt;br&gt;
Yes. Administrators set the permitted model catalog through policy, and the inline completion model is admin-configured (users cannot change it). Chat and agent surfaces expose a model picker constrained by org policy. Because data-handling terms vary by provider, the permitted model list is a security decision, not only a quality preference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. How do I keep sensitive files out of Copilot's context?&lt;/strong&gt;&lt;br&gt;
Use content exclusion configured at the organization or repository level through GitHub's admin surfaces; it is enforced server-side and cannot be bypassed by editor settings. Pair it with secret scanning and push protection. A client-side ignore file is not an officially enforced control, so do not rely on it for real security requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Is Copilot HIPAA compliant?&lt;/strong&gt;&lt;br&gt;
The controlling principle is that PHI must never enter Copilot's prompt context, achieved through content exclusion and repository permissions, alongside a Business Associate arrangement and legal review of GitHub's terms. Compliance is a property of your configuration and controls, not a checkbox; healthcare rollouts typically require additional validation time and conservative enablement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What compliance certifications does Copilot have?&lt;/strong&gt;&lt;br&gt;
Copilot is included in GitHub's independent third-party audit program covering standards such as SOC 2 and ISO/IEC, with GDPR-relevant controls including data residency and a Data Processing Agreement. The authoritative, current source is the GitHub Copilot Trust Center; obtain current attestations through official channels and confirm the certified scope covers the surfaces you use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Does Copilot support data residency?&lt;/strong&gt;&lt;br&gt;
Yes, through GitHub Enterprise Cloud with data residency, which by 2026 lets organizations choose the region where code, metadata, and Copilot usage telemetry are stored and processed, with supported regions including EU, US, Australia, and Japan and more planned. Note that model inference for some providers may still occur outside your region depending on their infrastructure, so validate the exact scope with your account team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. How much does Copilot cost for an enterprise?&lt;/strong&gt;&lt;br&gt;
As of 2026, organizational seats are priced per user per month, with Business and Enterprise at different tiers, and everyday completions unlimited. Premium features (chat, agents, code review) meter against a monthly AI-credit allowance with usage-based overage. Budget seats and AI credits separately, because agent-heavy usage consumes credits at a very different rate than completion-heavy usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Can Copilot write an entire feature autonomously?&lt;/strong&gt;&lt;br&gt;
The coding agent can take an issue, work in an isolated environment, and open a pull request for a well-scoped change, and agent mode can execute multi-file edits with tests in the IDE. Neither should merge without human review and passing checks. Autonomy is appropriate for well-tested, lower-risk work and should be expanded conservatively; a human still owns what ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. What is agent mode versus the coding agent?&lt;/strong&gt;&lt;br&gt;
Agent mode runs in the IDE, keeps you in the loop on edits, and uses your selected chat model. The coding agent is a hosted async service that takes a task or issue and opens a PR, with its own per-task model picker. The IDE gate is your approval of edits; the coding-agent gate is the pull request review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. Does Copilot work with JetBrains, VS Code, and the command line?&lt;/strong&gt;&lt;br&gt;
Yes. Agent mode is generally available in VS Code and JetBrains, completions and chat work across major IDEs, and the Copilot CLI is a separate install that brings agentic workflows, sub-agents, repository memory, and a built-in MCP server to the terminal. Surface availability of specific models varies, so check the supported-models documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. How do I make Copilot follow our coding conventions?&lt;/strong&gt;&lt;br&gt;
Use instruction files in version control: a repository-level instructions file encodes conventions and preferred libraries, personal and organization instructions layer on top, and agent-definition files define custom agents per project. Because they are reviewed like source, conventions become consistent and auditable across the team rather than dependent on individual prompting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. Will Copilot introduce security vulnerabilities?&lt;/strong&gt;&lt;br&gt;
It can reproduce insecure patterns from training data, so generated code must face the same security controls as human code: OWASP Top 10 review, CodeQL scanning, secret scanning, and dependency scanning. Copilot also helps find issues earlier and Autofix can remediate scanning alerts. Net security improves only when the surrounding controls are all in place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. What is Copilot Autofix?&lt;/strong&gt;&lt;br&gt;
Autofix for code scanning is a security-focused mechanism that proposes fixes for code-scanning alerts and integrates with CodeQL to help remediate and prevent vulnerabilities. GitHub is explicit that it will not fix every alert in every situation, so treat it as an accelerant for remediation rather than a guarantee, and keep human verification in the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. Can Copilot review pull requests?&lt;/strong&gt;&lt;br&gt;
Yes. Copilot code review reads a PR's diff with project context and surfaces likely defects, smells, and best-practice issues at the line level with suggested edits, combining model reasoning with deterministic engines. Organizations can enable it on all PRs including those from unlicensed contributors, billed as AI credits, and it consumes Actions minutes as of mid-2026. It is a first pass that complements, not replaces, human review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;16. How do I prevent AI-generated code from bypassing CI/CD?&lt;/strong&gt;&lt;br&gt;
Enforce branch protection with required status checks and required reviews on all branches that can reach production, applied identically to coding-agent PRs. Keep tests, static analysis, security scanning, and quality gates as required checks. Ensure no merge path exists that skips these gates; this is the core discipline of governing agentic engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. Does Copilot help with legacy modernization?&lt;/strong&gt;&lt;br&gt;
Substantially. Grounded chat provides rapid comprehension of undocumented legacy systems, and agentic surfaces support incremental, test-covered refactoring and translation of patterns across languages during cloud transformation. The bottleneck in modernization is understanding, and grounded comprehension is exactly where Copilot adds the most durable value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What are AI credits and how are they consumed?&lt;/strong&gt;&lt;br&gt;
AI credits are the metering unit for premium features. Everyday completions are unlimited, while chat, agents, and code review draw from a monthly allowance included with each seat, with usage beyond the allowance billed on a usage basis. Consumption depends heavily on workflow, so instrument it per cost center and use per-user budgets to control spend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. Can Copilot generate infrastructure as code?&lt;/strong&gt;&lt;br&gt;
Yes, across Terraform, Docker, Kubernetes, CloudFormation, and Bicep, and it is strong because these languages are pattern-dense. The discipline is identical to application code: specify security and lifecycle constraints, generate, then review against your guardrails and policy-as-code for encryption, least privilege, network isolation, and tagging. Copilot removes boilerplate; it does not remove responsibility for secure infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. How does Copilot handle context in large repositories?&lt;/strong&gt;&lt;br&gt;
The binding constraint is relevance, not raw window size. Copilot prioritizes the active file, recent files, and in-scope symbols, and on Enterprise uses repository indexing for retrievable codebase context. Well-factored code with clear boundaries retrieves better, so code organization directly affects suggestion quality, which is a new incentive for good architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21. Is there IP indemnity with Copilot?&lt;/strong&gt;&lt;br&gt;
Organizational plans include GitHub's Copilot Copyright Commitment, which provides indemnification against certain third-party IP claims from suggestions, subject to conditions such as having the public-code duplication filter enabled. This is a meaningful advantage of Business and Enterprise over personal plans, and a reason to keep the duplication filter on rather than off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22. Can Copilot access our private repositories?&lt;/strong&gt;&lt;br&gt;
On grounded surfaces and through the coding agent, Copilot operates within the acting identity's permissions and can use indexed private repositories as context on Enterprise. It respects GitHub's access model, so your existing permissions and team boundaries govern what it can reach. Audit permissions before enabling grounded surfaces broadly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23. How do we handle developer resistance to Copilot?&lt;/strong&gt;&lt;br&gt;
Resistance is often legitimate and rooted in concerns about quality, skill erosion, or mandated tooling. Address it with enablement that respects engineers' judgment, transparency about limits, and demonstrations of value on real workflows rather than usage mandates. Positioning AI as an accelerant for accountable humans, with review practices that require understanding, builds durable adoption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24. What is the Copilot CLI good for?&lt;/strong&gt;&lt;br&gt;
The CLI brings agentic workflows to the terminal, with planning and autonomous modes, specialized sub-agents for exploration, tasks, review, and planning, repository memory across sessions, hooks and plugins, and a built-in GitHub MCP server. It is often the highest-leverage surface for DevOps, SRE, and platform engineers who work primarily in the shell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25. Does Copilot integrate with Azure DevOps, Jenkins, and GitLab?&lt;/strong&gt;&lt;br&gt;
The governing principle holds across all of them: Copilot-authored changes flow through your existing pipelines and gates. Copilot code review is extending into Azure Repos, and the coding agent can take work from issue trackers, but the merge gate remains human review plus automated checks in whatever CI/CD system you run. The coding agent itself operates on repositories hosted on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26. Can Copilot generate SQL and regex reliably?&lt;/strong&gt;&lt;br&gt;
It generates both effectively from described intent, and your responsibility is verification. For SQL, confirm the query plan and supporting indexes, because a correct query can still be an incident on a large table. For regex, always request positive and negative test strings so you validate the pattern rather than trusting it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;27. What models are available in Copilot in 2026?&lt;/strong&gt;&lt;br&gt;
The catalog spans multiple providers: OpenAI GPT-5 family and Codex variants, Anthropic Claude (Sonnet, Opus, Haiku classes), Google Gemini (Pro and Flash), xAI Grok, and Microsoft's coding-oriented models, with an Auto mode that selects per task. Availability differs by surface and org policy, and the list changes frequently, so consult the current supported-models documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;28. How do we measure ROI on Copilot?&lt;/strong&gt;&lt;br&gt;
Measure delivery outcomes, not activity counts. Track adoption by team, AI-credit consumption, and correlation with cycle time, review throughput, and defect escape, using the usage and metrics APIs including adoption-phase data. The meaningful question is whether delivery improved without quality regressing; suggestion counts alone are a vanity metric.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;29. Does Copilot replace human code review?&lt;/strong&gt;&lt;br&gt;
No. It provides a fast first pass that raises the floor by catching common defects and smells, but human review owns the ceiling: judgment, business context, architectural fit, and accountability. The accountability principle is that a human owns every line that ships, so human review and CODEOWNERS remain mandatory regardless of who authored the change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30. How should we start an enterprise Copilot rollout?&lt;/strong&gt;&lt;br&gt;
Standardize on Business or Enterprise; enforce SSO and content exclusion; restrict the model catalog and enable the public-code filter; require your full CI/CD and review gates on all PRs including coding-agent PRs; establish instruction files and a prompt library; enable agentic surfaces progressively on lower-risk systems; and instrument outcomes from day one. Run it as a measured program with a named owner who holds authority over policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;31. What happens to prompts sent to third-party model providers?&lt;/strong&gt;&lt;br&gt;
Model hosting and data commitments vary by provider; some operate under zero-data-retention policies, and prompts continue to pass through Copilot's content filters regardless of provider. Review the model-hosting documentation before enabling any provider organization-wide, and reflect the differences in your approved model catalog and compliance documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32. Can we enforce Copilot settings on developer machines?&lt;/strong&gt;&lt;br&gt;
Yes. In 2026, managed Copilot settings can be deployed to clients through mobile device management for the IDE and CLI, so configuration is enforced on managed devices rather than left to individual choice. This turns client configuration into an auditable control, which matters for regulated environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The following are authoritative primary sources. Prefer the official documentation and changelog over secondary summaries, because Copilot's surfaces change frequently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Copilot Documentation: &lt;a href="https://docs.github.com/en/copilot" rel="noopener noreferrer"&gt;https://docs.github.com/en/copilot&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Copilot Plans and Features: &lt;a href="https://docs.github.com/en/copilot/get-started/plans" rel="noopener noreferrer"&gt;https://docs.github.com/en/copilot/get-started/plans&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Copilot Trust Center: &lt;a href="https://copilot.github.trust.page" rel="noopener noreferrer"&gt;https://copilot.github.trust.page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Copilot Changelog: &lt;a href="https://github.blog/changelog/label/copilot/" rel="noopener noreferrer"&gt;https://github.blog/changelog/label/copilot/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Copilot Model Hosting Reference: &lt;a href="https://docs.github.com/en/copilot/reference/ai-models/model-hosting" rel="noopener noreferrer"&gt;https://docs.github.com/en/copilot/reference/ai-models/model-hosting&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Blog: &lt;a href="https://github.blog" rel="noopener noreferrer"&gt;https://github.blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Docs (platform): &lt;a href="https://docs.github.com" rel="noopener noreferrer"&gt;https://docs.github.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Actions Documentation: &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CodeQL Documentation: &lt;a href="https://codeql.github.com/docs/" rel="noopener noreferrer"&gt;https://codeql.github.com/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Advanced Security: &lt;a href="https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security" rel="noopener noreferrer"&gt;https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Microsoft Learn (Azure DevOps and Copilot): &lt;a href="https://learn.microsoft.com" rel="noopener noreferrer"&gt;https://learn.microsoft.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI Documentation: &lt;a href="https://platform.openai.com/docs" rel="noopener noreferrer"&gt;https://platform.openai.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Anthropic Documentation: &lt;a href="https://docs.anthropic.com" rel="noopener noreferrer"&gt;https://docs.anthropic.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OWASP Top 10: &lt;a href="https://owasp.org/www-project-top-ten/" rel="noopener noreferrer"&gt;https://owasp.org/www-project-top-ten/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Snyk Documentation: &lt;a href="https://docs.snyk.io" rel="noopener noreferrer"&gt;https://docs.snyk.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SonarQube Documentation: &lt;a href="https://docs.sonarsource.com" rel="noopener noreferrer"&gt;https://docs.sonarsource.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript Documentation: &lt;a href="https://www.typescriptlang.org/docs/" rel="noopener noreferrer"&gt;https://www.typescriptlang.org/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Java (JDK) Documentation: &lt;a href="https://docs.oracle.com/en/java/" rel="noopener noreferrer"&gt;https://docs.oracle.com/en/java/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python Documentation: &lt;a href="https://docs.python.org/3/" rel="noopener noreferrer"&gt;https://docs.python.org/3/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Documentation: &lt;a href="https://docs.docker.com" rel="noopener noreferrer"&gt;https://docs.docker.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes Documentation: &lt;a href="https://kubernetes.io/docs/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Terraform Documentation: &lt;a href="https://developer.hashicorp.com/terraform/docs" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Playwright Documentation: &lt;a href="https://playwright.dev/docs/intro" rel="noopener noreferrer"&gt;https://playwright.dev/docs/intro&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Engineering Practices (Code Review): &lt;a href="https://google.github.io/eng-practices/" rel="noopener noreferrer"&gt;https://google.github.io/eng-practices/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Martin Fowler on Refactoring and Architecture: &lt;a href="https://martinfowler.com" rel="noopener noreferrer"&gt;https://martinfowler.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot in 2026 is not an autocomplete feature; it is a governed engineering platform that spans completions, chat, IDE agent mode, an autonomous coding agent, a command-line agent, and a multi-vendor orchestration layer, all sitting on a model marketplace. For an enterprise, the value is real and large, but it is entirely conditional on discipline.&lt;/p&gt;

&lt;p&gt;The recurring theme is that Copilot's least differentiating part is the model. What determines outcomes is everything around it: context assembly and grounding, instruction files and prompt libraries, content exclusion and data-handling posture, compliance mapping and data residency, seat and AI-credit governance, and, above all, the merge gate that ensures AI-authored code faces the same automated and human review as human-authored code. Deployed with those controls, Copilot raises the floor on quality, accelerates the pattern-dense work that dominates real engineering, and turns comprehension of large systems from a bottleneck into a strength. Deployed without them, it is a faster way to ship unreviewed, inconsistent, and potentially insecure code.&lt;/p&gt;

&lt;p&gt;The practical mandate for platform and engineering leaders is to build the governance, review, and measurement infrastructure ahead of the autonomy curve. Standardize on organizational plans, enforce the controls, encode conventions as shared assets, enable agentic surfaces progressively starting with lower-risk systems, keep a human owning every line that ships, and run the whole rollout as a measured program with a named owner. The organizations that thrive with AI-assisted engineering will not be the ones with the most aggressive adoption; they will be the ones whose guardrails scaled ahead of their agents.&lt;/p&gt;




&lt;p&gt;Written by Himanshu Agarwal&lt;/p&gt;

&lt;p&gt;Enterprise Test Architect&lt;/p&gt;

&lt;p&gt;AI Engineering Author&lt;/p&gt;

&lt;p&gt;Generative AI Educator&lt;/p&gt;

&lt;p&gt;Automation Architect&lt;/p&gt;

</description>
      <category>ai</category>
      <category>github</category>
      <category>git</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>🚀 August Sale is Live — Get Up to 95% OFF All AI Playbooks (Code: AI95)</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Thu, 06 Aug 2026 10:11:53 +0000</pubDate>
      <link>https://dev.to/himanshuai/august-sale-is-live-get-up-to-95-off-all-ai-playbooks-code-ai95-44ad</link>
      <guid>https://dev.to/himanshuai/august-sale-is-live-get-up-to-95-off-all-ai-playbooks-code-ai95-44ad</guid>
      <description>&lt;p&gt;Hi,&lt;/p&gt;

&lt;p&gt;The HimanshuAI August Sale is now live!&lt;/p&gt;

&lt;p&gt;For a limited time, you can get up to 95% OFF on my complete collection of premium AI Engineering digital playbooks.&lt;/p&gt;

&lt;p&gt;🎟 Coupon Code: AI95&lt;/p&gt;

&lt;p&gt;Explore the collection:&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whether you're a QA Engineer, SDET, Software Developer, AI Engineer, or GenAI enthusiast, these playbooks are designed to help you build practical, production-ready skills.&lt;/p&gt;

&lt;p&gt;What you'll get&lt;/p&gt;

&lt;p&gt;✅ 150+ Premium AI eBooks&lt;br&gt;
✅ 25+ Bundles &amp;amp; Masterpacks&lt;br&gt;
✅ Cursor AI Mastery&lt;br&gt;
✅ Claude Code Engineering&lt;br&gt;
✅ MCP (Model Context Protocol)&lt;br&gt;
✅ RAG &amp;amp; AI Agents&lt;br&gt;
✅ Prompt Engineering&lt;br&gt;
✅ LLM Engineering&lt;br&gt;
✅ AI Testing &amp;amp; Evaluation&lt;br&gt;
✅ Playwright &amp;amp; Selenium Automation&lt;br&gt;
✅ Python for AI Engineers&lt;br&gt;
✅ LangGraph &amp;amp; Agentic AI&lt;br&gt;
✅ Neural Networks &amp;amp; Machine Learning&lt;br&gt;
✅ Docker, Kubernetes &amp;amp; Cloud Engineering&lt;br&gt;
✅ Real Projects, Architecture &amp;amp; Best Practices&lt;br&gt;
✅ Interview Questions &amp;amp; Cheat Sheets&lt;br&gt;
✅ Lifetime Updates&lt;/p&gt;

&lt;p&gt;Every guide focuses on real implementation—not theory—using practical examples, production workflows, architecture diagrams, code samples, and engineering best practices.&lt;/p&gt;

&lt;p&gt;🎁 August Exclusive&lt;/p&gt;

&lt;p&gt;🔥 Up to 95% OFF&lt;br&gt;
🎟 Use Coupon Code: AI95&lt;br&gt;
⏳ Limited-Time Offer&lt;/p&gt;

&lt;p&gt;Browse all playbooks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Need recommendations or buying multiple bundles?&lt;/p&gt;

&lt;p&gt;📩 Simply reply to this email or DM me on LinkedIn. I'll personally help you choose the best learning path and, where applicable, offer up to 95% OFF on premium digital playbooks.&lt;/p&gt;

&lt;p&gt;Thank you for being part of the HimanshuAI community.&lt;/p&gt;

&lt;p&gt;Happy Learning!&lt;/p&gt;

&lt;p&gt;Himanshu Agarwal&lt;br&gt;
Founder, HimanshuAI&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Playwright AI: The Complete Test Automation Playbook (2026)</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Wed, 05 Aug 2026 11:26:50 +0000</pubDate>
      <link>https://dev.to/himanshuai/playwright-ai-the-complete-test-automation-playbook-2026-8d2</link>
      <guid>https://dev.to/himanshuai/playwright-ai-the-complete-test-automation-playbook-2026-8d2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Agentic end-to-end testing with Playwright's Planner / Generator / Healer agents, the Model Context Protocol server, and accessibility-tree–first automation — engineered for QA, SDETs, and AI Engineers who ship.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Playwright started as a fast, cross-browser end-to-end framework. In the 1.56 release it became something else: the first mainstream test framework to ship &lt;strong&gt;first-party AI agents&lt;/strong&gt; that explore an app, write tests against a live browser, and repair failures on their own. This playbook treats "Playwright AI" as three concrete, shipping capabilities — not marketing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Playwright Test Agents&lt;/strong&gt; — &lt;code&gt;planner&lt;/code&gt;, &lt;code&gt;generator&lt;/code&gt;, &lt;code&gt;healer&lt;/code&gt; (built into the framework via &lt;code&gt;npx playwright init-agents&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright MCP&lt;/strong&gt; — the &lt;code&gt;@playwright/mcp&lt;/code&gt; Model Context Protocol server that lets any LLM drive a real browser through the &lt;strong&gt;accessibility tree&lt;/strong&gt;, not screenshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright CLI + Skills&lt;/strong&gt; — a token-efficient, command-driven alternative to MCP for coding agents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything here is grounded in the shipped APIs. Where a technique has trade-offs, they are stated plainly.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why This Technology Matters
&lt;/h2&gt;

&lt;p&gt;The economics of E2E testing were always lopsided: writing a test is cheap, &lt;strong&gt;maintaining&lt;/strong&gt; it is expensive. A renamed CSS class, a refactored component, or a 200ms slower modal turns a green pipeline red — and none of those are real bugs. Traditional selectors (&lt;code&gt;div.checkout-btn-v3&lt;/code&gt;) couple your test to implementation details that churn every sprint.&lt;/p&gt;

&lt;p&gt;Playwright AI attacks the maintenance tax on two fronts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem (pre-AI)&lt;/th&gt;
&lt;th&gt;Playwright AI mechanism&lt;/th&gt;
&lt;th&gt;Why it works&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Brittle CSS/XPath selectors&lt;/td&gt;
&lt;td&gt;Accessibility-tree locators (&lt;code&gt;role&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, ARIA)&lt;/td&gt;
&lt;td&gt;ARIA attributes change far less than CSS classes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual selector repair after UI drift&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Healer agent&lt;/strong&gt; re-inspects live page, patches locator&lt;/td&gt;
&lt;td&gt;Grounded in the running DOM, not a stale snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow, developer-imagined test coverage&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Planner&lt;/strong&gt; explores the real app, writes a plan&lt;/td&gt;
&lt;td&gt;Covers paths users take, not paths devs assume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshot-based AI automation (slow, non-deterministic)&lt;/td&gt;
&lt;td&gt;MCP structured snapshots&lt;/td&gt;
&lt;td&gt;No vision model, deterministic tool calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; the value is &lt;em&gt;maintenance reduction&lt;/em&gt;, not free test generation. If your UI rarely changes or your suite is tiny, the agent setup overhead may not pay off yet.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  3. Architecture
&lt;/h2&gt;

&lt;p&gt;The 2026 Playwright AI stack is layered. MCP (or the CLI) is the &lt;em&gt;structured browser access&lt;/em&gt; layer; the three agents sit on top as the &lt;em&gt;test lifecycle&lt;/em&gt; layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    subgraph Human["Human / CI"]
        DEV[Engineer or Pipeline]
    end
    subgraph AILayer["AI Client Layer"]
        LLM[LLM: Claude / Copilot / Codex]
    end
    subgraph Agents["Playwright Test Agents"]
        P[Planner] --&amp;gt; G[Generator] --&amp;gt; H[Healer]
    end
    subgraph Access["Structured Browser Access"]
        MCP["@playwright/mcp (a11y tree)"]
        CLI["playwright-cli + Skills"]
    end
    subgraph Runtime["Runtime"]
        BROWSER[(Chromium / Firefox / WebKit)]
        APP[(Application Under Test)]
    end

    DEV --&amp;gt; LLM
    LLM --&amp;gt; P
    Agents --&amp;gt; MCP
    Agents --&amp;gt; CLI
    MCP --&amp;gt; BROWSER
    CLI --&amp;gt; BROWSER
    BROWSER --&amp;gt; APP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ASCII view of the request path:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Engineer prompt
      │
      ▼
   LLM client ──► Agent definition (.md in /agents) ──► MCP tools
                                                          │
                          browser_snapshot / browser_click / browser_navigate
                                                          │
                                                          ▼
                                        Real browser  ◄──►  App under test
                                                          │
                                          accessibility snapshot returned
                                                          │
                                                          ▼
                                   Generated .spec.ts  /  healed locator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Core Components
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Output / Interface&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Planner agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Explores a running app, reasons about flows&lt;/td&gt;
&lt;td&gt;Markdown test plan (&lt;code&gt;specs/*-plan.md&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generator agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Converts a reviewed plan into runnable code, verifying locators live&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tests/*.spec.ts&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Healer agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs failing tests, distinguishes drift from real bugs, patches or skips&lt;/td&gt;
&lt;td&gt;Updated spec / skipped test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;@playwright/mcp&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MCP server exposing browser tools over accessibility snapshots&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;browser_*&lt;/code&gt; tools (snapshot, click, type, navigate, network)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;playwright-cli&lt;/code&gt; + Skills&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CLI wrapping the same automation as concise commands/skills&lt;/td&gt;
&lt;td&gt;Terminal commands, lower token cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ARIA snapshots&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Assert against the accessibility tree&lt;/td&gt;
&lt;td&gt;&lt;code&gt;expect(locator).toMatchAriaSnapshot()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trace Viewer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Post-mortem of every action, network call, DOM state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;trace.zip&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The agents are &lt;strong&gt;definitions&lt;/strong&gt; (Markdown instruction files) plus tool access — not a hosted service. The LLM does the reasoning; Playwright supplies grounded tools.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Internal Working
&lt;/h2&gt;

&lt;p&gt;The defining design choice is &lt;strong&gt;accessibility-tree-first&lt;/strong&gt; automation. Instead of feeding a model pixels, the MCP server serializes the page into a structured snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; button "Checkout" [ref=e12]
&lt;span class="p"&gt;-&lt;/span&gt; textbox "Email" [ref=e7]
&lt;span class="p"&gt;-&lt;/span&gt; link "Cart (3)" [ref=e3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model reasons over &lt;code&gt;role&lt;/code&gt; + accessible &lt;code&gt;name&lt;/code&gt; + a stable &lt;code&gt;ref&lt;/code&gt;, then issues a deterministic tool call (&lt;code&gt;browser_click { ref: "e12" }&lt;/code&gt;). Three consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No vision model needed&lt;/strong&gt; → cheaper, faster, reproducible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locators emitted resolve&lt;/strong&gt; because the generator drove a &lt;em&gt;live&lt;/em&gt; browser, not static HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Healing is grounded&lt;/strong&gt; — the Healer re-snapshots the real page and picks the best available role/text locator, rather than guessing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Critically, the Healer will &lt;strong&gt;skip&lt;/strong&gt; a test if the app itself is broken (e.g., checkout genuinely fails) rather than rewriting the assertion to hide the bug. That single rule is what separates "self-healing" from "self-lying."&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Step-by-Step Workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant E as Engineer
    participant PL as Planner
    participant GE as Generator
    participant BR as Browser (MCP)
    participant HE as Healer
    participant CI as CI

    E-&amp;gt;&amp;gt;PL: "Explore checkout flow"
    PL-&amp;gt;&amp;gt;BR: navigate + snapshot
    BR--&amp;gt;&amp;gt;PL: a11y tree
    PL--&amp;gt;&amp;gt;E: specs/checkout-plan.md (review)
    E-&amp;gt;&amp;gt;GE: "Generate tests for plan"
    GE-&amp;gt;&amp;gt;BR: replay steps, verify locators live
    BR--&amp;gt;&amp;gt;GE: resolved refs
    GE--&amp;gt;&amp;gt;E: tests/checkout.spec.ts
    E-&amp;gt;&amp;gt;CI: commit + run
    CI--&amp;gt;&amp;gt;HE: failure (drifted locator)
    HE-&amp;gt;&amp;gt;BR: re-snapshot, diagnose
    HE--&amp;gt;&amp;gt;CI: patched spec OR skip (real bug)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop is &lt;strong&gt;explore → plan → generate → run → heal&lt;/strong&gt;, with a &lt;strong&gt;human approval gate after each phase&lt;/strong&gt;. Never merge agent output un-reviewed.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Real Engineering Example
&lt;/h2&gt;

&lt;p&gt;A generated, resilient login spec using role-based locators and an ARIA snapshot assertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/login.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authentication&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;valid user reaches dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Role + accessible name: survives CSS refactors&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qa@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;textbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Str0ng!Pass&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert against the accessibility tree, not brittle markup&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toMatchAriaSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
      - heading "Dashboard" [level=1]
      - navigation "Primary"
    `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;dashboard/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;fallback locator helper&lt;/strong&gt; for cases where role-based lookup is ambiguous — the pattern a Healer effectively encodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// utils/resilientLocator.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resilient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt;&lt;span class="p"&gt;)[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Locator&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// exactly one match wins&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No unique locator resolved from fallback chain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;resilient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form &amp;gt;&amp;gt; text=Submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Production Use Cases
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Layer used&lt;/th&gt;
&lt;th&gt;Payoff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Coverage-debt backlog on a legacy app&lt;/td&gt;
&lt;td&gt;Planner + Generator&lt;/td&gt;
&lt;td&gt;Bulk-author plans from real flows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-churn design-system migration&lt;/td&gt;
&lt;td&gt;Healer in CI&lt;/td&gt;
&lt;td&gt;Auto-patch drifted locators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exploratory bug hunting&lt;/td&gt;
&lt;td&gt;MCP + LLM (interactive)&lt;/td&gt;
&lt;td&gt;Persistent context, iterative probing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR-scoped test selection&lt;/td&gt;
&lt;td&gt;CLI trace analysis + agents&lt;/td&gt;
&lt;td&gt;Run only affected specs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-browser regression&lt;/td&gt;
&lt;td&gt;Standard Playwright runner&lt;/td&gt;
&lt;td&gt;Agent output is plain &lt;code&gt;.spec.ts&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Agent-generated tests are &lt;strong&gt;ordinary Playwright tests&lt;/strong&gt;. They run unchanged in GitHub Actions, GitLab CI, Jenkins, or Azure Pipelines. The AI is a &lt;em&gt;development-time&lt;/em&gt; tool; the artifact is boring and portable — exactly what you want.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  9. Folder Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-app-e2e/
├── agents/                     # generated by `init-agents` (regenerate on PW upgrade)
│   ├── planner.md
│   ├── generator.md
│   └── healer.md
├── specs/                      # human-readable Markdown plans (Planner output)
│   └── checkout-plan.md
├── tests/                      # runnable specs (Generator output)
│   ├── login.spec.ts
│   └── checkout.spec.ts
├── utils/
│   └── resilientLocator.ts
├── fixtures/
│   └── auth.setup.ts           # storageState / auth fixtures
├── seed/
│   └── seed.spec.ts            # seed test the Planner starts from
├── playwright.config.ts
├── .mcp.json                   # MCP server config (optional)
├── package.json
└── .github/workflows/e2e.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Install Playwright (agents require v1.56+)&lt;/span&gt;
npm init playwright@latest

&lt;span class="c"&gt;# 2. Add the AI agent definitions (pick your client loop)&lt;/span&gt;
npx playwright init-agents &lt;span class="nt"&gt;--loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;claude     &lt;span class="c"&gt;# Claude Code&lt;/span&gt;
npx playwright init-agents &lt;span class="nt"&gt;--loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vscode      &lt;span class="c"&gt;# VS Code + Copilot (needs VS Code 1.105+)&lt;/span&gt;
npx playwright init-agents &lt;span class="nt"&gt;--loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;codex       &lt;span class="c"&gt;# OpenAI Codex&lt;/span&gt;
npx playwright init-agents &lt;span class="nt"&gt;--loop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;opencode    &lt;span class="c"&gt;# OpenCode&lt;/span&gt;

&lt;span class="c"&gt;# 3. (Optional) Run the MCP server standalone&lt;/span&gt;
npx @playwright/mcp@latest

&lt;span class="c"&gt;# 4. (Optional) Docker MCP — headless chromium only&lt;/span&gt;
docker run &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="nt"&gt;--pull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always mcr.microsoft.com/playwright/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimal MCP client config (works with Claude Code, Cursor, VS Code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"playwright"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@playwright/mcp@latest"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; the &lt;code&gt;init-agents&lt;/code&gt; initializer belongs to the &lt;strong&gt;Node.js&lt;/strong&gt; Playwright Test surface. Do not assume Python/Java/.NET parity for the agent workflow.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  11. Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;playwright.config.ts&lt;/code&gt; tuned for CI reliability and trace-driven debugging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fullyParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;forbidOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// fail if test.only slips into CI&lt;/span&gt;
  &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on-first-retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;// trace only when it matters&lt;/span&gt;
    &lt;span class="na"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;only-on-failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;testIdAttribute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-testid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;actionTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chromium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Safari&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restricting MCP surface for a generator agent (least privilege) via CLI flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @playwright/mcp@latest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowed-origins&lt;/span&gt; &lt;span class="s2"&gt;"https://staging.example.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--blocked-origins&lt;/span&gt; &lt;span class="s2"&gt;"https://*.analytics.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--isolated&lt;/span&gt; &lt;span class="nt"&gt;--storage-state&lt;/span&gt; ./storage.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  12. Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prefer role/name locators&lt;/strong&gt; (&lt;code&gt;getByRole&lt;/code&gt;, &lt;code&gt;getByLabel&lt;/code&gt;) over CSS/XPath; fall back to &lt;code&gt;getByTestId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regenerate agent definitions&lt;/strong&gt; after every Playwright upgrade — they encode tool schemas that change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-review every plan and every generated spec&lt;/strong&gt; before merge. The gate is non-negotiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the Planner a clean seed test&lt;/strong&gt; with auth/setup; it copies setup logic into each generated file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin agent evals in CI&lt;/strong&gt; so a model regression surfaces as a failing eval, not a silent quality drop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;trace: 'on-first-retry'&lt;/code&gt;&lt;/strong&gt; — full tracing on green runs is wasted storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope MCP access narrowly&lt;/strong&gt; for generator/healer vs. a general assistant; block third-party origins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep ARIA snapshots small&lt;/strong&gt; — assert the meaningful landmarks, not the whole tree.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  13. Common Mistakes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Consequence&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Merging Healer patches un-reviewed&lt;/td&gt;
&lt;td&gt;Healer masks a real regression&lt;/td&gt;
&lt;td&gt;Approval gate; require diff review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treating a passing rerun as root cause&lt;/td&gt;
&lt;td&gt;False confidence&lt;/td&gt;
&lt;td&gt;A green heal ≠ verified fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Over-broad MCP origins&lt;/td&gt;
&lt;td&gt;Data exfiltration / test hitting prod&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;--allowed-origins&lt;/code&gt;, &lt;code&gt;--isolated&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stale agent definitions after upgrade&lt;/td&gt;
&lt;td&gt;Missing tools, weird failures&lt;/td&gt;
&lt;td&gt;Re-run &lt;code&gt;init-agents&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Using screenshots for AI automation&lt;/td&gt;
&lt;td&gt;Slow, flaky, non-deterministic&lt;/td&gt;
&lt;td&gt;Use a11y snapshots (MCP default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-waits ignored, manual &lt;code&gt;sleep()&lt;/code&gt; added&lt;/td&gt;
&lt;td&gt;Flaky timing&lt;/td&gt;
&lt;td&gt;Rely on Playwright web-first assertions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Committing &lt;code&gt;trace.zip&lt;/code&gt; with secrets&lt;/td&gt;
&lt;td&gt;Leaked tokens/PII&lt;/td&gt;
&lt;td&gt;Traces can contain sensitive network data — gitignore + retention policy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  14. Performance Optimization
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shard across CI runners:&lt;/strong&gt; &lt;code&gt;--shard=1/4&lt;/code&gt; … &lt;code&gt;--shard=4/4&lt;/code&gt; to parallelize a large suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse auth state&lt;/strong&gt; via &lt;code&gt;storageState&lt;/code&gt; instead of logging in per test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI test-impact analysis:&lt;/strong&gt; select only specs affected by a PR diff to cut execution 40–75% (tools: Launchable, Tricentis LiveCompare, Appsurify).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer CLI + Skills over MCP&lt;/strong&gt; for coding agents — MCP loads large tool schemas and verbose a11y trees into context; CLI commands are far more token-efficient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fullyParallel: true&lt;/code&gt;&lt;/strong&gt; with tuned &lt;code&gt;workers&lt;/code&gt; — match to runner vCPUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache browsers&lt;/strong&gt; in CI (&lt;code&gt;~/.cache/ms-playwright&lt;/code&gt;).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Sharded parallel execution&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--shard&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1/4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  15. Security Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;MCP &lt;code&gt;--allowed-origins&lt;/code&gt; / &lt;code&gt;--blocked-origins&lt;/code&gt; are &lt;strong&gt;convenience filters, not a security boundary&lt;/strong&gt; — they do not affect redirects. Never point an agent at production with real credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traces and snapshots may contain PII, tokens, and full network bodies.&lt;/strong&gt; Treat &lt;code&gt;trace.zip&lt;/code&gt; as sensitive; set retention and access controls.&lt;/li&gt;
&lt;li&gt;Run MCP with &lt;strong&gt;least privilege&lt;/strong&gt;: &lt;code&gt;--isolated&lt;/code&gt;, scoped storage state, no clipboard/geolocation grants unless required.&lt;/li&gt;
&lt;li&gt;In CI, use &lt;strong&gt;short-lived, scoped secrets&lt;/strong&gt; and branch protection so an agent PR cannot self-merge.&lt;/li&gt;
&lt;li&gt;Prefer &lt;strong&gt;staging environments&lt;/strong&gt; with synthetic data for agent exploration.&lt;/li&gt;
&lt;li&gt;Keep the browser &lt;strong&gt;sandboxed&lt;/strong&gt; (&lt;code&gt;--no-sandbox&lt;/code&gt; only inside disposable containers).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  16. Scaling Strategies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph LR
    A[Single dev, few specs] --&amp;gt;|grows| B[Team suite in CI]
    B --&amp;gt;|churn rises| C[Healer in nightly job]
    C --&amp;gt;|coverage debt| D[Scheduled Planner exploration]
    D --&amp;gt;|scale| E[Sharded CI + impact analysis]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start small:&lt;/strong&gt; add agents to an &lt;em&gt;existing&lt;/em&gt; Playwright project, not greenfield.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nightly Healer&lt;/strong&gt; job to absorb drift before the morning pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduled autonomous Planner&lt;/strong&gt; runs to surface coverage gaps in large orgs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shard + impact analysis&lt;/strong&gt; to keep wall-clock time flat as the suite grows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Independent agent metrics&lt;/strong&gt; (planner coverage, generator pass-rate, healer patch validity) evaluated separately so failures are attributable.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  17. CI/CD Integration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/e2e.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;E2E&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;shard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;20&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright install --with-deps&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright test --shard=${{ matrix.shard }}/4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ !cancelled() }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report-${{ matrix.shard }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report/&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agent output requires &lt;strong&gt;no special CI&lt;/strong&gt; — a healed &lt;code&gt;.spec.ts&lt;/code&gt; runs like any other test. Keep the &lt;em&gt;interactive&lt;/em&gt; agent work at development time; CI runs the deterministic result.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Testing Strategy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    U[Unit / Component] --&amp;gt; I[Integration / API]
    I --&amp;gt; E[E2E: Playwright]
    E --&amp;gt; AG[AI Agents: plan/generate/heal]
    AG -.audited by.-&amp;gt; H[Human review + evals]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright AI sits at the &lt;strong&gt;E2E tip of the pyramid&lt;/strong&gt; — it does not replace unit, API, contract, accessibility, security, performance, or real-device testing. Use agents to &lt;em&gt;reduce E2E authoring and maintenance cost&lt;/em&gt;, and keep human judgment for what to test and whether a repair is legitimate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decision tree — should you use an agent here?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is the failure a locator drift? ── yes ──► Healer
        │ no
        ▼
New scenario to cover? ── yes ──► Generator (with reviewed plan)
        │ no
        ▼
Unknown app area? ── yes ──► Planner exploration
        │ no
        ▼
Write / fix by hand (edge case, complex assertion)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  19. Debugging Guide
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;Tool / fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Strict-mode violation&lt;/td&gt;
&lt;td&gt;Locator matches &amp;gt;1 element&lt;/td&gt;
&lt;td&gt;Narrow with role+name; &lt;code&gt;Trace Viewer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flaky timing failure&lt;/td&gt;
&lt;td&gt;Manual waits / animation&lt;/td&gt;
&lt;td&gt;Web-first assertions; &lt;code&gt;expect().toBeVisible()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Healer keeps skipping&lt;/td&gt;
&lt;td&gt;It's a real bug, not drift&lt;/td&gt;
&lt;td&gt;Fix the app — that's the point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent "does nothing"&lt;/td&gt;
&lt;td&gt;No LLM model active in client&lt;/td&gt;
&lt;td&gt;Activate model (VS Code agent panel / Claude Code)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Locators don't resolve&lt;/td&gt;
&lt;td&gt;Generated from stale HTML&lt;/td&gt;
&lt;td&gt;Ensure generator drives live app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP tool errors&lt;/td&gt;
&lt;td&gt;Origin blocked / redirect&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;--allowed-origins&lt;/code&gt;, redirects bypass filters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Debug interactively&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt;            &lt;span class="c"&gt;# inspector&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--ui&lt;/span&gt;               &lt;span class="c"&gt;# time-travel UI mode&lt;/span&gt;
npx playwright show-trace trace.zip    &lt;span class="c"&gt;# post-mortem&lt;/span&gt;
npx playwright codegen https://app.dev &lt;span class="c"&gt;# record baseline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  20. Interview Questions (50)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Conceptual&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What are Playwright Test Agents?&lt;/strong&gt; Three official agent definitions — planner, generator, healer — that explore an app, author tests, and repair failures against a live browser (shipped in v1.56).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why accessibility-tree over screenshots?&lt;/strong&gt; Structured, deterministic, no vision model; ARIA changes less than CSS, so locators are more stable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Planner vs Generator vs Healer?&lt;/strong&gt; Planner → Markdown plan; Generator → &lt;code&gt;.spec.ts&lt;/code&gt;; Healer → diagnoses/patches failing tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is a self-healed test trustworthy?&lt;/strong&gt; Not automatically — a passing rerun doesn't prove root cause; review is required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What does the Healer do when the app is genuinely broken?&lt;/strong&gt; Skips the test rather than hiding the bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is Playwright MCP?&lt;/strong&gt; An MCP server exposing browser automation via accessibility snapshots to any LLM client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI+Skills vs MCP — when each?&lt;/strong&gt; CLI for token-efficient coding agents; MCP for persistent, iterative agentic loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command to add agents?&lt;/strong&gt; &lt;code&gt;npx playwright init-agents --loop=&amp;lt;claude|vscode|codex|opencode&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum Playwright version for agents?&lt;/strong&gt; 1.56.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where do agent definitions live?&lt;/strong&gt; In &lt;code&gt;/agents&lt;/code&gt; as Markdown; regenerate on upgrade.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why regenerate definitions after an upgrade?&lt;/strong&gt; New releases change tool schemas/instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How does the generator avoid dead locators?&lt;/strong&gt; It drives a live browser and verifies refs before emitting code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What are the layers of the AI stack?&lt;/strong&gt; Structured access (MCP/CLI) + lifecycle agents + human/eval gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do agents replace the Playwright runner?&lt;/strong&gt; No — output is standard &lt;code&gt;.spec.ts&lt;/code&gt; run by the normal runner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How are agent runs stateful?&lt;/strong&gt; They aren't between sessions; each run is independent, so prompt clarity matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is an ARIA snapshot assertion?&lt;/strong&gt; &lt;code&gt;toMatchAriaSnapshot()&lt;/code&gt; asserts against the accessibility tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How would you isolate agent browser state?&lt;/strong&gt; &lt;code&gt;--isolated&lt;/code&gt; with a scoped &lt;code&gt;storage-state&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where does MCP fit for exploratory automation?&lt;/strong&gt; Persistent context + rich introspection over page structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What client versions gate the VS Code agent UX?&lt;/strong&gt; VS Code 1.105+.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which clients support the agent loop?&lt;/strong&gt; VS Code/Copilot, Claude Code, Codex, OpenCode.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A button ID changed and CI is red — action?&lt;/strong&gt; Run the Healer; it re-snapshots and patches the locator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New feature, no coverage — action?&lt;/strong&gt; Planner explores → review plan → Generator emits specs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent-generated test flakes on timing — fix?&lt;/strong&gt; Replace manual waits with web-first assertions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You must run agents on prod — concern?&lt;/strong&gt; Don't; origins aren't a security boundary and traces leak data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large legacy app, coverage debt — approach?&lt;/strong&gt; Scheduled Planner runs to surface gaps, then generate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PR touches one module — how to cut CI time?&lt;/strong&gt; AI test-impact analysis to select affected specs only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Healer patched a test but hid a regression — root cause?&lt;/strong&gt; Merged without review; enforce approval gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP context is bloating your agent — remedy?&lt;/strong&gt; Switch to CLI+Skills for concise commands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-browser bug only in WebKit — approach?&lt;/strong&gt; Standard project matrix; agent output runs unchanged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suite wall-clock too long — scale?&lt;/strong&gt; Shard across runners + reuse &lt;code&gt;storageState&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Production / Performance&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trace strategy for CI?&lt;/strong&gt; &lt;code&gt;trace: 'on-first-retry'&lt;/code&gt; to avoid storing green-run traces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to reuse auth?&lt;/strong&gt; &lt;code&gt;storageState&lt;/code&gt; from a setup project/fixture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharding syntax?&lt;/strong&gt; &lt;code&gt;--shard=1/4&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why forbid &lt;code&gt;test.only&lt;/code&gt; in CI?&lt;/strong&gt; &lt;code&gt;forbidOnly: !!process.env.CI&lt;/code&gt; prevents accidental partial runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to attribute agent quality regressions?&lt;/strong&gt; Independent evals per agent pinned in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry policy trade-off?&lt;/strong&gt; Retries hide flakiness but also mask real intermittent bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache strategy for browsers?&lt;/strong&gt; Cache &lt;code&gt;~/.cache/ms-playwright&lt;/code&gt; in CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker tuning?&lt;/strong&gt; Match &lt;code&gt;workers&lt;/code&gt; to runner vCPUs; too many contends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where do secrets belong?&lt;/strong&gt; Short-lived scoped CI secrets, never in traces/committed state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention for artifacts?&lt;/strong&gt; Short (e.g., 7 days) since traces carry sensitive data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Debugging / Coding&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tool for time-travel debugging?&lt;/strong&gt; &lt;code&gt;--ui&lt;/code&gt; UI mode; &lt;code&gt;show-trace&lt;/code&gt; for post-mortem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix strict-mode violation?&lt;/strong&gt; Add role+name to make the locator unique.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record a baseline test?&lt;/strong&gt; &lt;code&gt;npx playwright codegen &amp;lt;url&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a resilient locator fallback chain — sketch it.&lt;/strong&gt; Iterate candidate locators; return the one with exactly one match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert on a landmark region?&lt;/strong&gt; &lt;code&gt;expect(page.getByRole('main')).toMatchAriaSnapshot(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why &lt;code&gt;count() === 1&lt;/code&gt; in fallback logic?&lt;/strong&gt; Guarantees uniqueness, avoiding strict-mode errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug an agent that does nothing?&lt;/strong&gt; Confirm an LLM model is active in the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle redirect bypassing allowed-origins?&lt;/strong&gt; Treat filters as non-security; segregate environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict a generator's MCP surface?&lt;/strong&gt; Narrow origins, isolated context, minimal grants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagnose "locator resolves locally, fails in CI"?&lt;/strong&gt; Environment/data drift, timing, or headless differences — inspect the trace.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  21. FAQs (30)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Do agents replace SDETs?&lt;/strong&gt; No — they shift effort from writing/maintaining to reviewing intent and repairs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which language do agents support?&lt;/strong&gt; The &lt;code&gt;init-agents&lt;/code&gt; flow targets Node.js/TypeScript Playwright Test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I use agents without VS Code?&lt;/strong&gt; Yes — Claude Code, Codex, OpenCode loops are supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are generated tests portable to CI?&lt;/strong&gt; Yes, they're plain &lt;code&gt;.spec.ts&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does MCP need a vision model?&lt;/strong&gt; No — it uses structured accessibility snapshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is MCP a security boundary?&lt;/strong&gt; No; origin filters don't affect redirects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When is CLI better than MCP?&lt;/strong&gt; For high-throughput coding agents needing token efficiency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What version introduced agents?&lt;/strong&gt; 1.56.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do I regenerate agents on upgrade?&lt;/strong&gt; Yes, always.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can the Healer create false positives?&lt;/strong&gt; Yes — a green rerun isn't a verified root cause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Will the Healer hide real bugs?&lt;/strong&gt; It's designed to skip, not mask; but review is still mandatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best locator strategy?&lt;/strong&gt; Role/label first, &lt;code&gt;data-testid&lt;/code&gt; fallback, CSS last.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I reduce flakiness?&lt;/strong&gt; Web-first assertions, no manual sleeps, retries sparingly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can agents run headless?&lt;/strong&gt; Yes; Docker MCP is headless Chromium only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I keep agents from touching prod?&lt;/strong&gt; Scoped origins + staging + no prod creds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do traces contain secrets?&lt;/strong&gt; They can — treat as sensitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's a seed test?&lt;/strong&gt; The starting test whose setup the Planner/Generator reuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I run one agent alone?&lt;/strong&gt; Yes — e.g., only the Healer on a break.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do agents store memory between runs?&lt;/strong&gt; No; each run is stateless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I evaluate agent quality?&lt;/strong&gt; Per-agent metrics pinned as CI evals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which browsers are supported?&lt;/strong&gt; Chromium, Firefox, WebKit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I speed up a huge suite?&lt;/strong&gt; Shard + impact analysis + &lt;code&gt;storageState&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is &lt;code&gt;toMatchAriaSnapshot&lt;/code&gt;?&lt;/strong&gt; Assertion against the accessibility tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I use it with Cursor?&lt;/strong&gt; Yes — add the MCP server in Cursor settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do agents replace unit tests?&lt;/strong&gt; No — they're an E2E-layer tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How big should ARIA snapshots be?&lt;/strong&gt; Minimal — key landmarks only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I connect to a logged-in browser tab?&lt;/strong&gt; Yes, via the Playwright MCP Chrome extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is component testing supported?&lt;/strong&gt; Yes, Playwright component testing is mature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How do I control time in tests?&lt;/strong&gt; The Clock API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where's the official doc?&lt;/strong&gt; &lt;code&gt;playwright.dev/docs/test-agents&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  22. Cheat Sheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;init agents      npx playwright init-agents --loop=claude
run MCP          npx @playwright/mcp@latest
run tests        npx playwright test
UI mode          npx playwright test --ui
debug            npx playwright test --debug
trace            npx playwright show-trace trace.zip
&lt;/span&gt;&lt;span class="gp"&gt;codegen          npx playwright codegen &amp;lt;url&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;shard            npx playwright test --shard=1/4
report           npx playwright show-report
install deps     npx playwright install --with-deps
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Locator priority:&lt;/strong&gt; &lt;code&gt;getByRole&lt;/code&gt; → &lt;code&gt;getByLabel&lt;/code&gt; → &lt;code&gt;getByTestId&lt;/code&gt; → &lt;code&gt;getByText&lt;/code&gt; → CSS/XPath (last resort).&lt;/p&gt;




&lt;h2&gt;
  
  
  23. Useful Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init playwright@latest                     &lt;span class="c"&gt;# scaffold project&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;chromium         &lt;span class="c"&gt;# single browser&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test &lt;/span&gt;tests/login.spec.ts        &lt;span class="c"&gt;# single file&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s2"&gt;"checkout"&lt;/span&gt;              &lt;span class="c"&gt;# grep by title&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--headed&lt;/span&gt; &lt;span class="nt"&gt;--workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1       &lt;span class="c"&gt;# watch it run&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--update-snapshots&lt;/span&gt;         &lt;span class="c"&gt;# refresh snapshots&lt;/span&gt;
npx playwright merge-reports ./blob-reports     &lt;span class="c"&gt;# combine shards&lt;/span&gt;
npx @playwright/mcp@latest &lt;span class="nt"&gt;--help&lt;/span&gt;              &lt;span class="c"&gt;# MCP flags&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  24. Real Project Walkthrough
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Goal:&lt;/strong&gt; cover a "add employee" flow on an existing app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Seed&lt;/strong&gt; — write a minimal &lt;code&gt;seed/seed.spec.ts&lt;/code&gt; handling login/storageState.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Init&lt;/strong&gt; — &lt;code&gt;npx playwright init-agents --loop=vscode&lt;/code&gt; (creates &lt;code&gt;/agents&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; — prompt the Planner: &lt;em&gt;"Explore and plan the add-employee flow."&lt;/em&gt; → &lt;code&gt;specs/add-employee-plan.md&lt;/code&gt; with scenarios, preconditions, expected/failure criteria. &lt;strong&gt;Review it.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt; — prompt the Generator: &lt;em&gt;"Generate tests for the 'Adding an Employee' section."&lt;/em&gt; → &lt;code&gt;tests/add-employee-tc001.spec.ts&lt;/code&gt;, one file per scenario, setup copied from the seed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run&lt;/strong&gt; — &lt;code&gt;npx playwright test&lt;/code&gt;. Some specs pass; one fails on a drifted locator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heal&lt;/strong&gt; — invoke the Healer on the failing test; it re-inspects the live page, swaps to a role-based locator, reruns to confirm. &lt;strong&gt;Review the diff.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship&lt;/strong&gt; — commit the reviewed specs; CI runs them sharded across 4 runners.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Outcome: intent (Markdown) → grounded code → self-repair, with humans gating every transition.&lt;/p&gt;




&lt;h2&gt;
  
  
  25. Learning Roadmap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mindmap
  root((Playwright AI))
    Foundations
      Locators &amp;amp; auto-wait
      Fixtures &amp;amp; config
      Trace Viewer
    AI Layer
      MCP server
      CLI + Skills
      init-agents
    Agents
      Planner
      Generator
      Healer
    Production
      Sharding
      CI/CD
      Impact analysis
    Governance
      Review gates
      Evals
      Security
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sequence:&lt;/strong&gt; locators &amp;amp; auto-waiting → fixtures/config → Trace Viewer → MCP basics → agents (plan/generate/heal) → CI sharding → evals &amp;amp; governance.&lt;/p&gt;




&lt;h2&gt;
  
  
  26. Additional Resources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Official docs&lt;/td&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;&lt;a href="https://playwright.dev" rel="noopener noreferrer"&gt;https://playwright.dev&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official docs&lt;/td&gt;
&lt;td&gt;Test Agents&lt;/td&gt;
&lt;td&gt;&lt;a href="https://playwright.dev/docs/test-agents" rel="noopener noreferrer"&gt;https://playwright.dev/docs/test-agents&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/microsoft/playwright" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;Playwright MCP&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/microsoft/playwright-mcp" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright-mcp&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;Playwright CLI&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/microsoft/playwright-cli" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright-cli&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spec&lt;/td&gt;
&lt;td&gt;Model Context Protocol&lt;/td&gt;
&lt;td&gt;&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;https://modelcontextprotocol.io&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blog&lt;/td&gt;
&lt;td&gt;Playwright (DEV, Debbie O'Brien)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/playwright"&gt;https://dev.to/playwright&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;Playwright Discord&lt;/td&gt;
&lt;td&gt;&lt;a href="https://aka.ms/playwright/discord" rel="noopener noreferrer"&gt;https://aka.ms/playwright/discord&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;td&gt;r/Playwright&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.reddit.com/r/Playwright/" rel="noopener noreferrer"&gt;https://www.reddit.com/r/Playwright/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;Trace Viewer&lt;/td&gt;
&lt;td&gt;&lt;a href="https://playwright.dev/docs/trace-viewer" rel="noopener noreferrer"&gt;https://playwright.dev/docs/trace-viewer&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;ARIA snapshots&lt;/td&gt;
&lt;td&gt;&lt;a href="https://playwright.dev/docs/aria-snapshots" rel="noopener noreferrer"&gt;https://playwright.dev/docs/aria-snapshots&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI&lt;/td&gt;
&lt;td&gt;Playwright + GitHub Actions&lt;/td&gt;
&lt;td&gt;&lt;a href="https://playwright.dev/docs/ci-intro" rel="noopener noreferrer"&gt;https://playwright.dev/docs/ci-intro&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  27. Checklists
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Production readiness&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Playwright ≥ 1.56, agents regenerated after last upgrade&lt;/li&gt;
&lt;li&gt;[ ] Human review gate on plans, generated specs, and heals&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;trace: 'on-first-retry'&lt;/code&gt;, &lt;code&gt;screenshot: 'only-on-failure'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;forbidOnly&lt;/code&gt; enabled in CI&lt;/li&gt;
&lt;li&gt;[ ] Sharding + browser cache configured&lt;/li&gt;
&lt;li&gt;[ ] Role/label locators dominate; &lt;code&gt;data-testid&lt;/code&gt; fallback in place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Agents never point at production with real creds&lt;/li&gt;
&lt;li&gt;[ ] MCP origins scoped; &lt;code&gt;--isolated&lt;/code&gt; used&lt;/li&gt;
&lt;li&gt;[ ] Traces gitignored, retention + access controls set&lt;/li&gt;
&lt;li&gt;[ ] Short-lived scoped CI secrets; branch protection on agent PRs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code review&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No manual &lt;code&gt;sleep()&lt;/code&gt;; web-first assertions only&lt;/li&gt;
&lt;li&gt;[ ] No strict-mode violations&lt;/li&gt;
&lt;li&gt;[ ] ARIA snapshots minimal and meaningful&lt;/li&gt;
&lt;li&gt;[ ] Healed diffs verify root cause, not just a green rerun&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Playwright AI is not a magic button — it's a &lt;strong&gt;grounded pipeline&lt;/strong&gt;: MCP/CLI gives models structured, accessibility-tree access to a real browser; the Planner, Generator, and Healer turn intent into reviewable Markdown, runnable TypeScript, and honest repairs. The measurable win is &lt;strong&gt;maintenance reduction&lt;/strong&gt; on high-churn UIs, not free test creation. Treat every agent output as a proposal behind a human gate, scope its access tightly, keep traces sensitive, and evaluate each agent independently. Do that, and you convert the old E2E maintenance tax into review-time judgment — which is exactly where engineers add value.&lt;/p&gt;




&lt;h1&gt;
  
  
  🎁 Continue Your AI Engineering Journey
&lt;/h1&gt;

&lt;p&gt;If you found this guide valuable and want complete digital playbooks covering AI Engineering, MCP, RAG, LLMs, AI Testing, Agentic AI, Prompt Engineering, LangGraph, Cursor AI, Playwright AI, Python, and many more advanced topics…&lt;/p&gt;

&lt;p&gt;Visit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://himanshuai.gumroad.com/" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want an even bigger discount?&lt;/p&gt;

&lt;p&gt;📩 DM me on LinkedIn.&lt;/p&gt;

&lt;p&gt;I'll personally give you up to &lt;strong&gt;95% OFF&lt;/strong&gt; on my premium digital playbooks.&lt;/p&gt;

&lt;p&gt;Created with ❤️ by&lt;/p&gt;

&lt;h1&gt;
  
  
  Himanshu Agarwal
&lt;/h1&gt;

&lt;p&gt;Follow for practical AI Engineering content.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>playwright</category>
      <category>testing</category>
    </item>
    <item>
      <title>DeepEval for AI Testing: The Complete End-to-End Engineering Guide</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Tue, 04 Aug 2026 15:26:15 +0000</pubDate>
      <link>https://dev.to/himanshuai/deepeval-for-ai-testing-the-complete-end-to-end-engineering-guide-4f7f</link>
      <guid>https://dev.to/himanshuai/deepeval-for-ai-testing-the-complete-end-to-end-engineering-guide-4f7f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; Himanshu Agarwal&lt;br&gt;
&lt;strong&gt;Level:&lt;/strong&gt; Intermediate → Advanced&lt;br&gt;
&lt;strong&gt;Focus:&lt;/strong&gt; Testing, evaluating, and shipping reliable LLM &amp;amp; RAG applications with DeepEval&lt;br&gt;
&lt;strong&gt;Reading time:&lt;/strong&gt; ~30 minutes&lt;/p&gt;
&lt;/blockquote&gt;




&lt;blockquote&gt;
&lt;h3&gt;
  
  
  🎁 Featured Resource — GenAI Engineering Vault (16 Books Bundle)
&lt;/h3&gt;

&lt;p&gt;If you are building production-grade AI systems and want the &lt;em&gt;entire&lt;/em&gt; engineering playbook in one place — evaluation, RAG, agents, prompt engineering, LLMOps, and deployment — grab the full bundle here:&lt;br&gt;
&lt;strong&gt;👉 &lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;GenAI Engineering Vault — 16 Books Bundle&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Explore all my playbooks at &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com" rel="noopener noreferrer"&gt;himanshuai.gumroad.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why AI Testing Is a First-Class Engineering Problem&lt;/li&gt;
&lt;li&gt;What Is DeepEval?&lt;/li&gt;
&lt;li&gt;Why Choose DeepEval Over Rolling Your Own Evals&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;End-to-End Installation (Step by Step)&lt;/li&gt;
&lt;li&gt;Recommended Project Folder Structure&lt;/li&gt;
&lt;li&gt;Core Concepts You Must Understand&lt;/li&gt;
&lt;li&gt;Your First Evaluation (Single-Turn)&lt;/li&gt;
&lt;li&gt;Multi-Turn &amp;amp; Conversational Testing&lt;/li&gt;
&lt;li&gt;The Metrics Deep Dive&lt;/li&gt;
&lt;li&gt;Component-Level Evals With Tracing&lt;/li&gt;
&lt;li&gt;Synthetic Data Generation&lt;/li&gt;
&lt;li&gt;Running DeepEval in CI/CD&lt;/li&gt;
&lt;li&gt;Framework Integrations&lt;/li&gt;
&lt;li&gt;A Complete Worked Example: End-to-End RAG Chatbot Evaluation&lt;/li&gt;
&lt;li&gt;Advanced Patterns &amp;amp; Best Practices&lt;/li&gt;
&lt;li&gt;Troubleshooting Common Issues&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;Frequently Asked Questions (FAQs)&lt;/li&gt;
&lt;li&gt;Final Thoughts&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Why AI Testing Is a First-Class Engineering Problem
&lt;/h2&gt;

&lt;p&gt;Traditional software is deterministic. Feed a function the same input twice and you get the same output twice. That predictability is what makes conventional unit tests trustworthy — you assert &lt;code&gt;add(2, 2) == 4&lt;/code&gt; and move on with your life.&lt;/p&gt;

&lt;p&gt;Large Language Model (LLM) applications break this assumption completely. The same prompt can produce different phrasing, different reasoning paths, and occasionally, a confidently wrong answer. A model that scored beautifully on Monday can quietly regress on Friday after a prompt tweak, a temperature change, a new retrieval chunking strategy, or a silent upstream model update from your provider.&lt;/p&gt;

&lt;p&gt;This is the core reason AI testing deserves to be treated as a first-class engineering discipline rather than a "we'll eyeball the outputs" afterthought. When your chatbot, RAG pipeline, or autonomous agent is in front of real users, "it looked fine in the demo" is not a quality strategy — it's a liability.&lt;/p&gt;

&lt;p&gt;The problems you actually need to catch include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hallucinations&lt;/strong&gt; — the model inventing facts that are not grounded in the provided context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Irrelevance&lt;/strong&gt; — answers that technically respond but miss the user's actual intent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faithfulness failures in RAG&lt;/strong&gt; — the generation contradicting the retrieved documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety issues&lt;/strong&gt; — toxicity, bias, PII leakage, or jailbreak susceptibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regressions&lt;/strong&gt; — a change that improves one scenario while silently breaking three others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic failures&lt;/strong&gt; — wrong tool calls, broken task completion, or reasoning that spirals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You cannot manually re-check hundreds of these scenarios every time you push a commit. What you need is an evaluation framework that turns these fuzzy quality questions into scored, repeatable, automatable tests. That is exactly the gap DeepEval fills.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What Is DeepEval?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DeepEval&lt;/strong&gt; is an open-source LLM evaluation framework (Apache 2.0 licensed) built by the team behind Confident AI. If pytest is the standard way to unit-test Python code, DeepEval is designed to be the standard way to unit-test LLM outputs.&lt;/p&gt;

&lt;p&gt;At its heart, DeepEval lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write &lt;strong&gt;evaluation tests&lt;/strong&gt; for LLM outputs the same way you write pytest tests.&lt;/li&gt;
&lt;li&gt;Score outputs using &lt;strong&gt;50+ research-backed metrics&lt;/strong&gt; — including faithfulness, answer relevancy, contextual precision/recall, hallucination, bias, and toxicity.&lt;/li&gt;
&lt;li&gt;Evaluate &lt;strong&gt;end-to-end&lt;/strong&gt; (treating your app as a black box) &lt;em&gt;and&lt;/em&gt; &lt;strong&gt;component-level&lt;/strong&gt; (scoring individual tool calls, retrievers, and sub-agents through tracing).&lt;/li&gt;
&lt;li&gt;Generate &lt;strong&gt;synthetic datasets&lt;/strong&gt; for edge cases that are painful to collect by hand.&lt;/li&gt;
&lt;li&gt;Plug evaluations directly into &lt;strong&gt;CI/CD&lt;/strong&gt; so regressions are caught before they reach production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DeepEval is &lt;strong&gt;local-first&lt;/strong&gt; — your evaluations run in your own environment, and you only need an LLM provider key (like &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;) for the metrics that use an LLM as a judge. It is also &lt;strong&gt;model-agnostic&lt;/strong&gt; and &lt;strong&gt;framework-agnostic&lt;/strong&gt;: it works with OpenAI, Anthropic, Gemini, Azure OpenAI, Ollama, and local/custom models, and integrates natively with LangChain, LangGraph, LlamaIndex, CrewAI, Pydantic AI, OpenAI Agents, Google ADK, and more.&lt;/p&gt;

&lt;p&gt;The optional cloud companion, &lt;strong&gt;Confident AI&lt;/strong&gt;, sits on top of DeepEval and adds shared dashboards, regression tracking, observability, and production monitoring — but you never &lt;em&gt;need&lt;/em&gt; it to run evaluations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A useful mental model:&lt;/strong&gt; Observability tools tell you &lt;em&gt;what happened&lt;/em&gt;. DeepEval tells you &lt;em&gt;whether what happened was good enough&lt;/em&gt;, by running metrics against test cases, traces, spans, and datasets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;A word on design philosophy.&lt;/strong&gt; DeepEval treats your LLM app as a black box by default — you don't have to expose internals to evaluate the final output. When you &lt;em&gt;do&lt;/em&gt; want to look inside (to evaluate an agent's individual steps), tracing is opt-in and non-intrusive, meaning it never changes how your code runs. This "black box first, glass box when you need it" philosophy is deliberate: it lets a beginner get a passing eval in five minutes, while giving an advanced team the depth to score every tool call, retriever, and sub-agent in a complex pipeline. You grow into the complexity rather than being forced to confront all of it on day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Why Choose DeepEval Over Rolling Your Own Evals
&lt;/h2&gt;

&lt;p&gt;Plenty of teams start by writing a quick &lt;code&gt;if "sorry" in output: fail&lt;/code&gt; script. It works for a week. Then it collapses under the weight of real-world nuance. Here is why a dedicated framework wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Research-backed metrics out of the box.&lt;/strong&gt; Metrics like &lt;code&gt;GEval&lt;/code&gt; (an LLM-as-a-judge metric with human-like accuracy) and RAG-specific metrics like faithfulness and contextual recall are already implemented and tuned. You do not reinvent them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pytest-native ergonomics.&lt;/strong&gt; DeepEval feels like the testing you already know. &lt;code&gt;deepeval test run&lt;/code&gt; slots into any workflow that already uses pytest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A first-class regression story.&lt;/strong&gt; Run more than one test run and you can compare test cases side by side to catch improvements &lt;em&gt;and&lt;/em&gt; regressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracing for agents.&lt;/strong&gt; Modern AI apps are multi-step. DeepEval's non-intrusive &lt;code&gt;@observe&lt;/code&gt; tracing lets you score individual components without rewriting your architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synthetic data + benchmarks.&lt;/strong&gt; You get tooling to generate edge cases and run standard benchmarks, not just a metric library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A serious community and ecosystem.&lt;/strong&gt; 250+ contributors, 20+ integrations, and active development (DeepEval 4.0 is the current major line).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bottom line: a home-grown eval script optimizes for &lt;em&gt;today's&lt;/em&gt; demo. DeepEval optimizes for the messy, evolving reality of a system you have to maintain for months.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before installing, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.9+&lt;/strong&gt; installed (&lt;code&gt;python --version&lt;/code&gt; to check).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pip&lt;/strong&gt; available and reasonably up to date.&lt;/li&gt;
&lt;li&gt;A terminal you are comfortable in (macOS/Linux shell or Windows PowerShell/CMD).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An LLM provider API key.&lt;/strong&gt; Most DeepEval metrics are LLM-as-a-judge metrics, so you'll typically want an &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;. You can swap in Anthropic, Gemini, Azure, Ollama, or a custom local model later.&lt;/li&gt;
&lt;li&gt;Basic familiarity with &lt;strong&gt;pytest&lt;/strong&gt; concepts (test functions, assertions) — helpful but not mandatory.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always work inside a virtual environment. It keeps your evaluation dependencies isolated from your application dependencies and prevents version conflicts.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. End-to-End Installation (Step by Step)
&lt;/h2&gt;

&lt;p&gt;This section takes you from a clean machine to your first passing evaluation. Follow it in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Create and activate a virtual environment
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;deepeval-testing &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;deepeval-testing
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deepeval-testing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deepeval-testing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="nx"&gt;\Scripts\Activate.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your prompt should now show &lt;code&gt;(.venv)&lt;/code&gt;, confirming the environment is active.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Install DeepEval
&lt;/h3&gt;

&lt;p&gt;Inside the activated environment, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-U&lt;/span&gt; deepeval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-U&lt;/code&gt; flag ensures you get the latest version. This single package pulls in everything you need to run evaluations locally.&lt;/p&gt;

&lt;p&gt;Verify the install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deepeval &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the DeepEval CLI help output listing commands like &lt;code&gt;test run&lt;/code&gt;, &lt;code&gt;login&lt;/code&gt;, &lt;code&gt;view&lt;/code&gt;, and &lt;code&gt;inspect&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Configure your LLM judge (API key)
&lt;/h3&gt;

&lt;p&gt;Because most metrics use an LLM as a judge, set your provider key as an environment variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-your-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;setx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sk-your-key-here"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DeepEval also &lt;strong&gt;autoloads environment files&lt;/strong&gt; at import time. The precedence order is: existing process environment → &lt;code&gt;.env.local&lt;/code&gt; → &lt;code&gt;.env&lt;/code&gt;. So the cleanest approach for a project is a &lt;code&gt;.env.local&lt;/code&gt; file (which you should git-ignore):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.local&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sk-your-key-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you ever need to opt out of dotenv autoloading, set &lt;code&gt;DEEPEVAL_DISABLE_DOTENV=1&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — (Optional) Log in to Confident AI
&lt;/h3&gt;

&lt;p&gt;If you want centralized dashboards, regression reports, and production monitoring, connect to Confident AI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deepeval login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your browser handles authentication. After signing in, return to the terminal to confirm your name, organization, and first project. DeepEval automatically creates and saves a project API key. For CI or other non-interactive environments, pass a key directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deepeval login &lt;span class="nt"&gt;--api-key&lt;/span&gt; &amp;lt;your-confident-api-key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Remember:&lt;/strong&gt; This step is entirely optional. DeepEval runs perfectly well fully local.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 5 — Write your first test file
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;test_example.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;assert_test&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GEval&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_correctness&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;correctness_metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Correctness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Determine if the &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;actual output&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; is correct based on the &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;expected output&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evaluation_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTUAL_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EXPECTED_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I have a persistent cough and fever. Should I be worried?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A persistent cough and fever could be a viral infection or &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;something more serious. See a doctor if symptoms worsen or &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t improve in a few days.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A persistent cough and fever could indicate a range of illnesses, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from a mild viral infection to more serious conditions like &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pneumonia or COVID-19. Seek medical attention if symptoms worsen, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;persist, or include difficulty breathing or chest pain.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assert_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;correctness_metric&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6 — Run the evaluation
&lt;/h3&gt;

&lt;p&gt;From the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deepeval &lt;span class="nb"&gt;test &lt;/span&gt;run test_example.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DeepEval will run the metric, print a score between 0 and 1, and mark the test as passed ✅ if the score clears the &lt;code&gt;threshold&lt;/code&gt;. Congratulations — you have just run your first LLM evaluation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7 — (Optional) View and save results
&lt;/h3&gt;

&lt;p&gt;To push and view results on the cloud (requires login):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deepeval view
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To save results locally as JSON, set a results folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEEPEVAL_RESULTS_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./data"&lt;/span&gt;

&lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;span class="nb"&gt;set &lt;/span&gt;&lt;span class="nv"&gt;DEEPEVAL_RESULTS_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.&lt;span class="se"&gt;\d&lt;/span&gt;ata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the full loop: install → configure → write → run → inspect. Everything else in this guide builds on this foundation.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Recommended Project Folder Structure
&lt;/h2&gt;

&lt;p&gt;As your evaluation suite grows from one file to dozens, structure matters. A flat pile of &lt;code&gt;test_*.py&lt;/code&gt; files becomes unmaintainable fast. Here is a clean, scalable layout I recommend for a serious AI application with a real evaluation suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-ai-app/
├── app/                          # Your actual application code
│   ├── __init__.py
│   ├── rag_pipeline.py           # RAG retrieval + generation logic
│   ├── agent.py                  # Agent orchestration
│   └── prompts/
│       ├── system_prompt.txt
│       └── rag_prompt.txt
│
├── evals/                        # All evaluation code lives here
│   ├── __init__.py
│   │
│   ├── datasets/                 # Goldens &amp;amp; evaluation datasets
│   │   ├── rag_goldens.json
│   │   ├── agent_goldens.json
│   │   └── safety_goldens.json
│   │
│   ├── metrics/                  # Custom &amp;amp; configured metrics
│   │   ├── __init__.py
│   │   ├── correctness.py        # GEval correctness definition
│   │   └── domain_metrics.py     # Your custom domain metrics
│   │
│   ├── test_rag.py               # RAG evaluation suite
│   ├── test_agent.py             # Agent evaluation suite
│   ├── test_safety.py            # Bias / toxicity / safety suite
│   └── test_regression.py        # Golden regression suite
│
├── synthetic/                    # Synthetic data generation scripts
│   └── generate_goldens.py
│
├── data/                         # Local JSON results output
│   └── .gitkeep
│
├── .env.local                    # Secrets (GIT IGNORED)
├── .env.example                  # Template for teammates
├── .gitignore
├── requirements.txt
├── pytest.ini                    # Optional pytest config
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few principles behind this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate &lt;code&gt;app/&lt;/code&gt; from &lt;code&gt;evals/&lt;/code&gt;.&lt;/strong&gt; Your evaluation code should never leak into your production code path. Keeping them apart makes both easier to reason about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralize datasets.&lt;/strong&gt; Goldens (the inputs and expected outputs you evaluate against) are versioned assets. Treat them like data, not like code buried in test files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse metric definitions.&lt;/strong&gt; Define a &lt;code&gt;GEval&lt;/code&gt; correctness metric once in &lt;code&gt;evals/metrics/&lt;/code&gt; and import it everywhere. Don't copy-paste threshold values across ten files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate synthetic generation.&lt;/strong&gt; Data generation is a one-off/periodic task, not something that should run every test cycle. Give it its own folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git-ignore secrets and results.&lt;/strong&gt; &lt;code&gt;.env.local&lt;/code&gt; and &lt;code&gt;data/&lt;/code&gt; outputs should never hit version control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;deepeval&lt;/span&gt;
&lt;span class="err"&gt;openai&lt;/span&gt;
&lt;span class="err"&gt;python-dotenv&lt;/span&gt;
&lt;span class="err"&gt;pytest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a minimal &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;.&lt;span class="n"&gt;venv&lt;/span&gt;/
.&lt;span class="n"&gt;env&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt;/
&lt;span class="err"&gt;__&lt;/span&gt;&lt;span class="n"&gt;pycache__&lt;/span&gt;/
.&lt;span class="n"&gt;deepeval&lt;/span&gt;/
*.&lt;span class="n"&gt;pyc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;blockquote&gt;
&lt;h3&gt;
  
  
  🎁 Level Up — GenAI Engineering Vault (16 Books Bundle)
&lt;/h3&gt;

&lt;p&gt;Loving this structured, hands-on approach? The &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;GenAI Engineering Vault — 16 Books Bundle&lt;/a&gt;&lt;/strong&gt; goes far deeper across evaluation, RAG architecture, agent design, LLMOps, and production deployment — the exact playbooks I use for real systems.&lt;br&gt;
Browse the full catalog at &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com" rel="noopener noreferrer"&gt;himanshuai.gumroad.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Core Concepts You Must Understand
&lt;/h2&gt;

&lt;p&gt;Before you go further, internalize these four building blocks. Everything in DeepEval is composed from them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Case
&lt;/h3&gt;

&lt;p&gt;An &lt;code&gt;LLMTestCase&lt;/code&gt; is a &lt;strong&gt;single unit of LLM app interaction&lt;/strong&gt;. It has mandatory fields — &lt;code&gt;input&lt;/code&gt; (mimics the user's message) and &lt;code&gt;actual_output&lt;/code&gt; (what your app produced) — and optional fields like &lt;code&gt;expected_output&lt;/code&gt; and &lt;code&gt;retrieval_context&lt;/code&gt; (the chunks a RAG system retrieved). For multi-turn interactions, you use a &lt;code&gt;ConversationalTestCase&lt;/code&gt; made of &lt;code&gt;Turn&lt;/code&gt; objects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metric
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;metric&lt;/strong&gt; scores a test case. Every DeepEval metric score ranges from &lt;strong&gt;0 to 1&lt;/strong&gt;, and a &lt;code&gt;threshold&lt;/code&gt; (e.g. &lt;code&gt;0.5&lt;/code&gt;) determines pass/fail. Metrics fall into families: LLM-as-a-judge (like &lt;code&gt;GEval&lt;/code&gt;), RAG metrics (faithfulness, answer relevancy, contextual recall/precision), safety metrics (bias, toxicity), agentic metrics (task completion, tool correctness), and conversational metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Golden
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;golden&lt;/strong&gt; is a pre-defined evaluation example — typically an &lt;code&gt;input&lt;/code&gt; (and often an &lt;code&gt;expected_output&lt;/code&gt;) — that you store in a dataset and run your app against. Goldens are the seeds of a repeatable eval suite. You loop over them, feed each &lt;code&gt;input&lt;/code&gt; through your app to get an &lt;code&gt;actual_output&lt;/code&gt;, then score the resulting test cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dataset
&lt;/h3&gt;

&lt;p&gt;An &lt;code&gt;EvaluationDataset&lt;/code&gt; is a collection of goldens (or test cases). It's how you organize, version, and iterate over your evaluation examples at scale. Datasets are what make regression testing possible — you run the same dataset before and after a change and compare.&lt;/p&gt;

&lt;h3&gt;
  
  
  The mental flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Golden (input, expected_output)
        │
        ▼
Your LLM app  ──►  actual_output
        │
        ▼
LLMTestCase (input, actual_output, expected_output, retrieval_context)
        │
        ▼
Metric.measure()  ──►  score (0–1)  ──►  pass/fail vs threshold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Your First Evaluation (Single-Turn)
&lt;/h2&gt;

&lt;p&gt;You already ran a single-turn test in the installation section. Let's understand it more deeply and expand it.&lt;/p&gt;

&lt;p&gt;The star of the show is &lt;strong&gt;&lt;code&gt;GEval&lt;/code&gt;&lt;/strong&gt; — a research-backed, LLM-as-a-judge metric that lets you evaluate outputs against &lt;em&gt;any custom criteria&lt;/em&gt; you describe in plain English. This is enormously powerful: instead of hand-coding logic, you describe what "good" means and let a judge model score it with human-like nuance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AnswerRelevancyMetric&lt;/span&gt;

&lt;span class="c1"&gt;# Define a reusable correctness metric
&lt;/span&gt;&lt;span class="n"&gt;correctness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Correctness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Determine whether the actual output is factually correct &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;and complete compared to the expected output.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;evaluation_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTUAL_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EXPECTED_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;relevancy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;test_case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the capital of France?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The capital of France is Paris, a major European city.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Paris is the capital of France.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# evaluate() runs metrics without needing pytest discovery
&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_cases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_case&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;correctness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relevancy&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two ways to run evaluations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;deepeval test run&lt;/code&gt;&lt;/strong&gt; — the pytest-style path, using &lt;code&gt;assert_test&lt;/code&gt; inside &lt;code&gt;test_*&lt;/code&gt; functions. Best for CI/CD gating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;evaluate(...)&lt;/code&gt;&lt;/strong&gt; — a programmatic path you can call from any script. Best for notebooks, experiments, and batch runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also customize the judge model per metric:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;correctness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Correctness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;evaluation_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LLMTestCaseParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTUAL_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# or "o1", or a custom/local model object
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Multi-Turn &amp;amp; Conversational Testing
&lt;/h2&gt;

&lt;p&gt;Chatbots aren't single-shot. You need to evaluate whole conversations — tone, professionalism, coherence across turns, and whether the assistant stayed on task. DeepEval handles this with &lt;code&gt;ConversationalTestCase&lt;/code&gt; and conversational metrics like &lt;code&gt;ConversationalGEval&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;assert_test&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Turn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConversationalTestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationalGEval&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_professionalism&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;professionalism&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConversationalGEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Professionalism&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Determine whether the assistant acted professionally and &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;helpfully across the entire conversation.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConversationalTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;Turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is DeepEval?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DeepEval is an open-source LLM evaluation framework.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Can I use it in CI/CD?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Turn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Yes — it runs with pytest and gates regressions in CI.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assert_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;professionalism&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;role&lt;/code&gt; distinguishes the end user from your assistant, and &lt;code&gt;content&lt;/code&gt; holds each message. The metric evaluates the &lt;em&gt;sequence&lt;/em&gt; — not just a single reply — which is exactly what you need to catch a bot that starts strong but degrades over a long dialogue.&lt;/p&gt;

&lt;p&gt;For advanced multi-turn work, DeepEval also offers a &lt;strong&gt;Conversation Simulator&lt;/strong&gt; that can generate realistic multi-turn conversations to stress-test your assistant against scenarios you'd never think to script by hand.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. The Metrics Deep Dive
&lt;/h2&gt;

&lt;p&gt;DeepEval ships with 50+ metrics. You will never use all of them at once — you pick the ones that match what you're building. Here's how to think about the major families.&lt;/p&gt;

&lt;p&gt;Before diving into specifics, understand the two philosophical camps a metric can belong to. &lt;strong&gt;Reference-based metrics&lt;/strong&gt; compare your output against a known-correct answer (an &lt;code&gt;expected_output&lt;/code&gt;) — great when you have labeled data and a clear notion of "right." &lt;strong&gt;Reference-free metrics&lt;/strong&gt; judge quality without a gold answer — essential in production, where you rarely have the ideal answer sitting next to every real user query. Answer relevancy and faithfulness, for instance, can be assessed reference-free because they measure the output against the &lt;em&gt;question&lt;/em&gt; and the &lt;em&gt;retrieved context&lt;/em&gt; rather than a pre-written ideal. Knowing which camp a metric sits in tells you when you can use it: reference-based for curated test suites, reference-free for live monitoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAG Metrics (the big four)
&lt;/h3&gt;

&lt;p&gt;If you're building retrieval-augmented generation, these are your bread and butter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faithfulness&lt;/strong&gt; — Does the generated answer stay true to the retrieved context, or does it hallucinate beyond it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer Relevancy&lt;/strong&gt; — Is the answer actually relevant to the user's question?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Precision&lt;/strong&gt; — Are the most relevant retrieved chunks ranked highest?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contextual Recall&lt;/strong&gt; — Did retrieval surface &lt;em&gt;all&lt;/em&gt; the information needed to answer?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together these decompose RAG quality into a retrieval side and a generation side, which is critical for debugging — you learn &lt;em&gt;whether the problem is retrieval or generation&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;FaithfulnessMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextualPrecisionMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextualRecallMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;test_case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What are the side effects of the medication?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The common side effects include nausea and drowsiness.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Common side effects are nausea, drowsiness, and headache.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retrieval_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The medication may cause nausea, drowsiness, and headache in some patients.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;FaithfulnessMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;ContextualPrecisionMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;ContextualRecallMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt;
&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_cases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;test_case&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Safety Metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bias&lt;/strong&gt; — detects biased or discriminatory content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toxicity&lt;/strong&gt; — detects harmful, offensive, or abusive language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination&lt;/strong&gt; — flags content unsupported by the given context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are essential for anything user-facing. For adversarial red-teaming at scale (jailbreaks, prompt injection, PII leakage), the DeepEval team also maintains &lt;strong&gt;DeepTeam&lt;/strong&gt;, a dedicated red-teaming companion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agentic Metrics
&lt;/h3&gt;

&lt;p&gt;For agents and tool-using workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task Completion&lt;/strong&gt; — Did the agent actually accomplish the user's goal?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool Correctness&lt;/strong&gt; — Did it call the right tools with the right arguments?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Custom Metrics With GEval
&lt;/h3&gt;

&lt;p&gt;When no built-in metric fits, &lt;code&gt;GEval&lt;/code&gt; lets you define your own in plain language. Want to score "empathy" for a mental-health support bot, or "brand voice adherence" for marketing copy? Describe the criteria and let the judge model handle it. This flexibility is why &lt;code&gt;GEval&lt;/code&gt; is the workhorse of most real evaluation suites.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Component-Level Evals With Tracing
&lt;/h2&gt;

&lt;p&gt;End-to-end evaluation treats your app as a black box: input goes in, output comes out, you score the output. That's a great start. But modern AI apps are pipelines — a retriever, a re-ranker, a generator, maybe several tool calls and sub-agents. When an end-to-end test fails, a black-box score won't tell you &lt;em&gt;which component&lt;/em&gt; broke.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracing&lt;/strong&gt; solves this. DeepEval's &lt;code&gt;@observe&lt;/code&gt; decorator instruments individual functions in your pipeline as &lt;strong&gt;spans&lt;/strong&gt;, letting you attach metrics to specific components and score them in isolation. Critically, this instrumentation is &lt;strong&gt;non-intrusive&lt;/strong&gt; — it does not change how your code behaves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.dataset&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EvaluationDataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Golden&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.tracing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update_current_span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update_current_trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AnswerRelevancyMetric&lt;/span&gt;

&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EvaluationDataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goldens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Golden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Why is the sky blue?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

&lt;span class="nd"&gt;@observe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_ai_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;update_current_trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;

&lt;span class="nd"&gt;@observe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rayleigh scattering makes the sky appear blue.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The sky is blue due to Rayleigh scattering of sunlight.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;update_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;test_case&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;retrieval_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;golden&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evals_iterator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;my_ai_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;golden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happened here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;evals_iterator()&lt;/code&gt; looped through the dataset, capturing one trace per golden.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@observe&lt;/code&gt; created a span for each instrumented function.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;metrics=[...]&lt;/code&gt; attached to &lt;code&gt;generate&lt;/code&gt; scored &lt;em&gt;just that component&lt;/em&gt; once the trace finished.&lt;/li&gt;
&lt;li&gt;DeepEval aggregated everything into a single test run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the &lt;strong&gt;recommended way to evaluate AI agents&lt;/strong&gt;, because it tells you exactly where quality breaks down. You can also run &lt;code&gt;deepeval inspect&lt;/code&gt; to open a trace-tree TUI showing per-span scores and the judge's reasoning — invaluable for debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Synthetic Data Generation
&lt;/h2&gt;

&lt;p&gt;The hardest part of evaluation is often getting good test data. Real-world edge cases are rare, hard to collect, and expensive to label. DeepEval's &lt;strong&gt;Synthesizer&lt;/strong&gt; generates synthetic goldens for you — including tricky edge cases you'd struggle to think of manually.&lt;/p&gt;

&lt;p&gt;The high-level idea: point the Synthesizer at your documents or contexts, and it produces realistic &lt;code&gt;input&lt;/code&gt;/&lt;code&gt;expected_output&lt;/code&gt; pairs (goldens) that exercise your system across diverse scenarios. You then run your app against these goldens exactly like any other dataset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.synthesizer&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Synthesizer&lt;/span&gt;

&lt;span class="n"&gt;synthesizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Synthesizer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;goldens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;synthesizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_goldens_from_docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;document_paths&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app/prompts/knowledge_base.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Use these goldens like any other dataset
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.dataset&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EvaluationDataset&lt;/span&gt;
&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EvaluationDataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goldens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;goldens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns "we don't have enough test cases" from a blocker into a solved problem, and it's one of DeepEval's most underrated features for teams trying to build coverage fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Running DeepEval in CI/CD
&lt;/h2&gt;

&lt;p&gt;This is where evaluation stops being a nice-to-have and becomes a safety net. By gating your pipeline on evaluation tests, you catch regressions &lt;em&gt;before&lt;/em&gt; they ship — the same way unit tests catch broken code.&lt;/p&gt;

&lt;p&gt;Because DeepEval is pytest-native, wiring it into CI is straightforward. Here's a &lt;strong&gt;GitHub Actions&lt;/strong&gt; example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LLM Evaluation&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;eval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python -m pip install --upgrade pip&lt;/span&gt;
          &lt;span class="s"&gt;pip install -r requirements.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run DeepEval tests&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI_API_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;CONFIDENT_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CONFIDENT_API_KEY }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;deepeval test run evals/test_rag.py&lt;/span&gt;
          &lt;span class="s"&gt;deepeval test run evals/test_agent.py&lt;/span&gt;
          &lt;span class="s"&gt;deepeval test run evals/test_safety.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key practices for reliable CI evals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Store keys as secrets.&lt;/strong&gt; Never hardcode &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; — use your CI provider's secret store and pass it via &lt;code&gt;env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep CI datasets small and deterministic.&lt;/strong&gt; Full sweeps are expensive and slow. Run a focused, high-signal subset on every PR, and run the full suite nightly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle rate limits gracefully.&lt;/strong&gt; DeepEval retries transient errors (network/timeout and 5xx) once by default, with exponential backoff. If your judge provider is rate-limited, evals can appear stuck — provision adequate quota for CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate on thresholds.&lt;/strong&gt; A failing metric fails the test, which fails the job, which blocks the merge. That's the whole point — quality becomes a merge requirement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For non-interactive CI login to Confident AI, use &lt;code&gt;deepeval login --api-key ...&lt;/code&gt; or set &lt;code&gt;CONFIDENT_API_KEY&lt;/code&gt; directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Framework Integrations
&lt;/h2&gt;

&lt;p&gt;DeepEval doesn't force you to rewrite your stack. It ships adapters for the major agent and LLM frameworks so you can drop evaluation into whatever you already use. Supported integrations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LangChain&lt;/strong&gt; and &lt;strong&gt;LangGraph&lt;/strong&gt; — via a &lt;code&gt;CallbackHandler&lt;/code&gt; you pass to &lt;code&gt;invoke&lt;/code&gt;/&lt;code&gt;ainvoke&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt; — a drop-in replacement: swap &lt;code&gt;from openai import OpenAI&lt;/code&gt; for &lt;code&gt;from deepeval.openai import OpenAI&lt;/code&gt;, and every completion call becomes a scored LLM span.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic&lt;/strong&gt; — same drop-in pattern with &lt;code&gt;from deepeval.anthropic import Anthropic&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LlamaIndex&lt;/strong&gt; — register DeepEval's event handler against LlamaIndex's instrumentation dispatcher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CrewAI&lt;/strong&gt; — instrument the crew with &lt;code&gt;instrument_crewai()&lt;/code&gt; and attach metrics to agents, LLMs, or tools.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pydantic AI&lt;/strong&gt;, &lt;strong&gt;OpenAI Agents&lt;/strong&gt;, &lt;strong&gt;Google ADK&lt;/strong&gt;, &lt;strong&gt;AWS AgentCore&lt;/strong&gt;, &lt;strong&gt;Strands&lt;/strong&gt;, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick flavor of the OpenAI drop-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.tracing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LlmSpanContext&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AnswerRelevancyMetric&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# identical API surface to the normal OpenAI client
&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm_span_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;LlmSpanContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;()])):&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Why is the ocean salty?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point of these integrations is that instrumentation is &lt;strong&gt;additive&lt;/strong&gt;. You keep your architecture; DeepEval observes and scores it. There are 20+ integrations total, so whatever your stack looks like, there's likely a native path.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. A Complete Worked Example: End-to-End RAG Chatbot Evaluation
&lt;/h2&gt;

&lt;p&gt;Concepts click when you see them assembled into one real workflow. Let's walk through evaluating a documentation-support RAG chatbot from start to finish — the kind of system where a wrong or hallucinated answer directly erodes user trust. This ties together datasets, goldens, RAG metrics, and a regression-safe structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Curate a dataset of goldens
&lt;/h3&gt;

&lt;p&gt;First, capture representative questions your users actually ask, along with the ideal answer for each. Store them as JSON in &lt;code&gt;evals/datasets/rag_goldens.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"How do I reset my password?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expected_output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Go to Settings → Security → Reset Password, enter your current password, then set a new one."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"What's the maximum file upload size?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expected_output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The maximum file upload size is 50 MB per file."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Can I export my data to CSV?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expected_output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes. Open the dataset, click Export, and choose CSV as the format."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These goldens are your definition of correct behavior. They live in version control, so any change to them is a reviewable, meaningful diff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Load the goldens into a dataset
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.dataset&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EvaluationDataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Golden&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evals/datasets/rag_goldens.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;goldens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Golden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expected_output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EvaluationDataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goldens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;goldens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3 — Run each golden through your actual app
&lt;/h3&gt;

&lt;p&gt;You feed each golden's &lt;code&gt;input&lt;/code&gt; into your real RAG pipeline to produce an &lt;code&gt;actual_output&lt;/code&gt; and the &lt;code&gt;retrieval_context&lt;/code&gt; it used. This is critical: you are evaluating &lt;em&gt;your app&lt;/em&gt;, not a hypothetical one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.rag_pipeline&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;answer_question&lt;/span&gt;  &lt;span class="c1"&gt;# your real pipeline
&lt;/span&gt;
&lt;span class="n"&gt;test_cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;golden&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;goldens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# answer_question returns (answer_text, list_of_retrieved_chunks)
&lt;/span&gt;    &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;golden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;test_cases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;golden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;golden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;retrieval_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;retrieved_chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 — Score with the RAG metric suite
&lt;/h3&gt;

&lt;p&gt;Now apply the four RAG metrics so you can see both retrieval quality and generation quality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;FaithfulnessMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextualPrecisionMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContextualRecallMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;FaithfulnessMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;# generation stays true to context
&lt;/span&gt;    &lt;span class="nc"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="c1"&gt;# answer addresses the question
&lt;/span&gt;    &lt;span class="nc"&gt;ContextualPrecisionMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# best chunks ranked highest
&lt;/span&gt;    &lt;span class="nc"&gt;ContextualRecallMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;# all needed info retrieved
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_cases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_cases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5 — Interpret the results diagnostically
&lt;/h3&gt;

&lt;p&gt;Here's where the four-metric decomposition pays off. Read the scores as a diagnosis, not just a grade:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low contextual recall&lt;/strong&gt; → your retriever is missing relevant chunks. Fix chunking, embeddings, or top-k, not the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low contextual precision&lt;/strong&gt; → the right chunks exist but are buried by noise. Improve ranking or re-ranking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low faithfulness&lt;/strong&gt; → retrieval is fine, but the generator is hallucinating beyond the context. Tighten the generation prompt or lower temperature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low answer relevancy&lt;/strong&gt; → the answer wanders off-topic even when the facts are present. Refine the instruction to answer the question directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between "the RAG bot is bad" (useless) and "retrieval recall is 0.4, so we're not fetching the right documents" (actionable).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6 — Turn it into a regression gate
&lt;/h3&gt;

&lt;p&gt;Wrap the whole thing in a &lt;code&gt;test_&lt;/code&gt; function so &lt;code&gt;deepeval test run&lt;/code&gt; can gate CI. Run it before and after any change — a new embedding model, a reworded prompt, a different chunk size — and compare. Green means improvement, red means regression, and you catch problems before your users do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# evals/test_rag.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;assert_test&lt;/span&gt;
&lt;span class="c1"&gt;# ... build each test_case as above ...
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_rag_faithfulness_and_relevancy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;test_case&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;build_test_cases&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;assert_test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a complete, production-shaped RAG evaluation loop: curate goldens → run your app → score with RAG metrics → diagnose → gate in CI. Everything else in DeepEval is a variation on this same rhythm.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Advanced Patterns &amp;amp; Best Practices
&lt;/h2&gt;

&lt;p&gt;Once you're past the basics, these patterns separate a toy eval suite from a production-grade one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Define metrics once, import everywhere.&lt;/strong&gt; Put your &lt;code&gt;GEval&lt;/code&gt; definitions and thresholds in &lt;code&gt;evals/metrics/&lt;/code&gt;. A single source of truth means a threshold change propagates consistently and you avoid silent drift between test files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Version your goldens like data.&lt;/strong&gt; Store goldens as JSON in &lt;code&gt;evals/datasets/&lt;/code&gt; and commit them. When you change a golden, that diff is meaningful — it's a change to your definition of "correct."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Separate fast checks from deep sweeps.&lt;/strong&gt; Maintain a small, deterministic "smoke" dataset that runs on every PR, and a large comprehensive dataset that runs nightly or before releases. This balances signal against cost and speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Choose the right judge model.&lt;/strong&gt; LLM-as-a-judge quality depends on the judge. A stronger judge model gives more reliable scores but costs more and runs slower. For high-stakes metrics, use a strong judge; for cheap sanity checks, a smaller model is fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Prefer component-level evals for agents.&lt;/strong&gt; Black-box scores tell you &lt;em&gt;that&lt;/em&gt; something is wrong. Span-level tracing tells you &lt;em&gt;what&lt;/em&gt; is wrong. For any multi-step system, instrument the components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Watch your judge's cost and rate limits.&lt;/strong&gt; Every LLM-as-a-judge metric is an API call. A dataset of 500 goldens with 4 metrics each is 2,000 judge calls. Budget for it, and use async iteration (the default) to run goldens concurrently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Combine automated evals with spot-checking.&lt;/strong&gt; Automated metrics scale, but periodically read raw outputs yourself. Metrics can drift or miss context; human review keeps them honest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Treat regressions as bugs.&lt;/strong&gt; When a regression test goes red, don't just bump the threshold to make it pass. Investigate. A red row is signal, not noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Use synthetic data to build coverage fast, then curate.&lt;/strong&gt; Generate broadly with the Synthesizer, then hand-pick and refine the highest-value goldens. Generation gives you breadth; curation gives you quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Keep secrets out of everything.&lt;/strong&gt; &lt;code&gt;.env.local&lt;/code&gt; locally, secret stores in CI. Never commit a key. Ever.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Troubleshooting Common Issues
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;My evaluation seems stuck / hangs forever.&lt;/strong&gt;&lt;br&gt;
Almost always your judge LLM is failing — usually rate limits or insufficient quota. DeepEval retries transient errors (network/timeout, 5xx) once with exponential backoff, but a hard quota failure (like OpenAI's &lt;code&gt;insufficient_quota&lt;/code&gt;) is treated as non-retryable. Check your provider key, quota, and network access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;OPENAI_API_KEY&lt;/code&gt; not found.&lt;/strong&gt;&lt;br&gt;
Confirm the variable is set in your current shell (&lt;code&gt;echo $OPENAI_API_KEY&lt;/code&gt;), or that it's in &lt;code&gt;.env.local&lt;/code&gt;/&lt;code&gt;.env&lt;/code&gt;. Remember DeepEval's precedence: process env → &lt;code&gt;.env.local&lt;/code&gt; → &lt;code&gt;.env&lt;/code&gt;. If you disabled dotenv with &lt;code&gt;DEEPEVAL_DISABLE_DOTENV=1&lt;/code&gt;, you must set the variable directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My test file isn't discovered.&lt;/strong&gt;&lt;br&gt;
Put test files where pytest can find them — typically in a &lt;code&gt;tests/&lt;/code&gt; or &lt;code&gt;evals/&lt;/code&gt; folder, with the &lt;code&gt;test_&lt;/code&gt; prefix (e.g. &lt;code&gt;test_rag.py&lt;/code&gt;). Note: when you pass a file &lt;em&gt;explicitly&lt;/em&gt; to &lt;code&gt;deepeval test run evals/my_eval.py&lt;/code&gt;, DeepEval runs it regardless of name; the &lt;code&gt;test_&lt;/code&gt; prefix is only needed for automatic discovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scores feel inconsistent between runs.&lt;/strong&gt;&lt;br&gt;
LLM-as-a-judge metrics have inherent variance. Use a stronger, more deterministic judge model, set thresholds with a little margin, and average over enough goldens that a single noisy score doesn't flip your suite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI evals are slow and expensive.&lt;/strong&gt;&lt;br&gt;
Shrink your PR dataset to a high-signal subset, run async, and move exhaustive sweeps to a nightly schedule. Every metric is an API call — fewer goldens per PR means faster, cheaper gates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I want to use a local model instead of OpenAI.&lt;/strong&gt;&lt;br&gt;
DeepEval supports Ollama, Azure OpenAI, Anthropic, Gemini, and fully custom/local models as the judge. Configure the model per metric via the &lt;code&gt;model=&lt;/code&gt; parameter, or set it globally through DeepEval's model configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Official DeepEval&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DeepEval documentation: &lt;a href="https://deepeval.com/docs/introduction" rel="noopener noreferrer"&gt;https://deepeval.com/docs/introduction&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;5-minute quickstart: &lt;a href="https://deepeval.com/docs/getting-started" rel="noopener noreferrer"&gt;https://deepeval.com/docs/getting-started&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Metrics reference: &lt;a href="https://deepeval.com/docs/metrics-introduction" rel="noopener noreferrer"&gt;https://deepeval.com/docs/metrics-introduction&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub repository: &lt;a href="https://github.com/confident-ai/deepeval" rel="noopener noreferrer"&gt;https://github.com/confident-ai/deepeval&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Integrations directory: &lt;a href="https://deepeval.com/integrations" rel="noopener noreferrer"&gt;https://deepeval.com/integrations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Troubleshooting &amp;amp; FAQ: &lt;a href="https://deepeval.com/docs/troubleshooting" rel="noopener noreferrer"&gt;https://deepeval.com/docs/troubleshooting&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ecosystem&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confident AI (cloud platform): &lt;a href="https://www.confident-ai.com" rel="noopener noreferrer"&gt;https://www.confident-ai.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DeepTeam (LLM red-teaming): &lt;a href="https://trydeepteam.com" rel="noopener noreferrer"&gt;https://trydeepteam.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Community Discord: linked from the DeepEval site and GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My Playbooks (Himanshu Agarwal)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎁 &lt;strong&gt;GenAI Engineering Vault — 16 Books Bundle:&lt;/strong&gt; &lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;All playbooks:&lt;/strong&gt; &lt;a href="https://himanshuai.gumroad.com" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  19. Frequently Asked Questions (FAQs)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q1. Do I need Confident AI to use DeepEval?&lt;/strong&gt;&lt;br&gt;
No. DeepEval runs entirely locally. Confident AI is an optional cloud layer that adds shared dashboards, regression tracking, observability, and production monitoring. You can build and run a complete evaluation suite without ever signing up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q2. Do I have to use OpenAI as the judge model?&lt;/strong&gt;&lt;br&gt;
No. OpenAI is just the quickest default for examples. DeepEval is model-agnostic and supports Anthropic, Gemini, Azure OpenAI, Ollama, and custom/local models. You can set the judge per metric with the &lt;code&gt;model=&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q3. Is DeepEval free?&lt;/strong&gt;&lt;br&gt;
Yes. DeepEval is fully open-source under the Apache 2.0 license and free for any purpose. Confident AI has a free tier plus paid plans for teams needing advanced features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q4. What can I actually evaluate with it?&lt;/strong&gt;&lt;br&gt;
Chatbots, RAG pipelines, AI agents, MCP systems, tool-using workflows, summarizers, structured outputs, multimodal apps, and custom LLM workflows — at both the end-to-end (system) level and the component level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q5. How is DeepEval different from observability tools?&lt;/strong&gt;&lt;br&gt;
Observability tells you &lt;em&gt;what happened&lt;/em&gt; inside your app. DeepEval tells you &lt;em&gt;whether the behavior was good enough&lt;/em&gt; by running metrics against test cases, traces, spans, and datasets. They're complementary — use both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q6. Can I run DeepEval in CI/CD?&lt;/strong&gt;&lt;br&gt;
Yes, and it's a core use case. DeepEval is built to run with pytest and CI providers, so you can gate merges on LLM regression tests. A failing metric fails the job and blocks the PR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q7. Where should I put my test files?&lt;/strong&gt;&lt;br&gt;
Anywhere pytest can discover them — commonly a &lt;code&gt;tests/&lt;/code&gt; or &lt;code&gt;evals/&lt;/code&gt; folder, with the &lt;code&gt;test_&lt;/code&gt; prefix. When you pass a file path explicitly to &lt;code&gt;deepeval test run&lt;/code&gt;, the prefix isn't required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q8. Why does my evaluation get stuck?&lt;/strong&gt;&lt;br&gt;
Most often the judge model is rate-limited, out of quota, or slow. DeepEval retries transient errors once with backoff, but hard quota errors are non-retryable. Check your key, quota, and network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q9. What is a "golden"?&lt;/strong&gt;&lt;br&gt;
A golden is a pre-defined evaluation example (typically an input, and often an expected output) stored in a dataset. You run your app against goldens to produce test cases, then score them. Goldens are the foundation of repeatable, regression-safe evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q10. What's the difference between end-to-end and component-level evaluation?&lt;/strong&gt;&lt;br&gt;
End-to-end treats your app as a black box and scores the final output. Component-level uses tracing (&lt;code&gt;@observe&lt;/code&gt;) to score individual pieces — retrievers, tool calls, sub-agents — so you know exactly where quality breaks down. Component-level is recommended for agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q11. How many metrics does DeepEval have, and which should I start with?&lt;/strong&gt;&lt;br&gt;
50+. For RAG, start with faithfulness, answer relevancy, contextual precision, and contextual recall. For general correctness, use &lt;code&gt;GEval&lt;/code&gt;. For safety, add bias and toxicity. For agents, add task completion and tool correctness. Pick what matches your system rather than using everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q12. Can DeepEval generate test data for me?&lt;/strong&gt;&lt;br&gt;
Yes. The Synthesizer generates synthetic goldens — including hard-to-collect edge cases — from your documents and contexts, so you can build coverage quickly instead of hand-writing every case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q13. How do I handle cost when metrics are LLM calls?&lt;/strong&gt;&lt;br&gt;
Every LLM-as-a-judge metric is an API call, so budget accordingly. Run async (the default) for concurrency, keep PR datasets small, choose cheaper judges for low-stakes checks, and reserve strong judges and full sweeps for nightly/release runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q14. Does DeepEval support TypeScript?&lt;/strong&gt;&lt;br&gt;
Yes. DeepEval has SDKs in both Python and TypeScript, so JavaScript/TypeScript teams can evaluate their LLM apps too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q15. What's the fastest way to get started?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;pip install -U deepeval&lt;/code&gt;, set your &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;, write a &lt;code&gt;test_example.py&lt;/code&gt; with a &lt;code&gt;GEval&lt;/code&gt; metric and an &lt;code&gt;LLMTestCase&lt;/code&gt;, and run &lt;code&gt;deepeval test run test_example.py&lt;/code&gt;. You'll have a passing eval in about five minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AI testing is not optional anymore. The moment your LLM application touches real users, "it worked in the demo" stops being an acceptable answer. You need scored, repeatable, automatable evaluation that catches hallucinations, regressions, and safety issues &lt;em&gt;before&lt;/em&gt; they reach production — and DeepEval gives you exactly that, with a pytest-native workflow that feels familiar from day one.&lt;/p&gt;

&lt;p&gt;The path is clear: install it, structure your project properly, learn the four core concepts (test cases, metrics, goldens, datasets), write your first single-turn eval, then layer in RAG metrics, tracing for agents, synthetic data, and finally CI/CD gating. Do that, and quality stops being a hope and becomes a guarantee your pipeline enforces on every commit.&lt;/p&gt;

&lt;p&gt;Start small — one metric, one test case — and grow from there. Your future self, debugging a silent regression at 2 a.m., will thank you.&lt;/p&gt;




&lt;blockquote&gt;
&lt;h3&gt;
  
  
  🎁 Go Deeper — GenAI Engineering Vault (16 Books Bundle)
&lt;/h3&gt;

&lt;p&gt;This guide is a single piece of a much larger engineering picture. If you want the &lt;em&gt;complete&lt;/em&gt; system — evaluation, RAG, agents, prompt engineering, LLMOps, and production deployment, all battle-tested — get the full bundle:&lt;br&gt;
&lt;strong&gt;👉 &lt;a href="https://himanshuai.gumroad.com/l/GenAIEngineeringVault16Books" rel="noopener noreferrer"&gt;GenAI Engineering Vault — 16 Books Bundle&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
And explore every playbook I've published at &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com" rel="noopener noreferrer"&gt;himanshuai.gumroad.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by **Himanshu Agarwal&lt;/em&gt;&lt;em&gt;. If this helped, share it with an engineer who's shipping AI without a safety net.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>deeplearning</category>
      <category>deepeval</category>
    </item>
    <item>
      <title>MCP and Python: The Complete Guide to Building the Future of AI Integrations</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Tue, 04 Aug 2026 06:04:17 +0000</pubDate>
      <link>https://dev.to/himanshuai/mcp-and-python-the-complete-guide-to-building-the-future-of-ai-integrations-51ca</link>
      <guid>https://dev.to/himanshuai/mcp-and-python-the-complete-guide-to-building-the-future-of-ai-integrations-51ca</guid>
      <description>&lt;p&gt;&lt;em&gt;By Himanshu Agarwal&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you've spent any time around AI engineering in the last year, you've probably heard the term &lt;strong&gt;MCP&lt;/strong&gt; — Model Context Protocol — thrown around constantly. Maybe you've seen it in a GitHub README, in an Anthropic blog post, or in a Slack message from a colleague who just got a Claude-powered tool talking to their company's internal database in an afternoon. And if you're a Python developer, there's a good chance you've also wondered: &lt;em&gt;where exactly does Python fit into all this, and why does it seem to be the language of choice for building MCP servers?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article is a deep, practical answer to both questions. We'll walk through what MCP actually is, why it exists, how its architecture works, why Python has become such a natural fit for implementing it, and what advanced Python concepts you'll actually lean on once you start building real MCP servers instead of toy examples. By the end, you should have a working mental model good enough to start building — or to walk into an interview and talk about this stuff with real confidence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Problem MCP Was Built to Solve&lt;/li&gt;
&lt;li&gt;What Exactly Is MCP?&lt;/li&gt;
&lt;li&gt;The Core Architecture: Hosts, Clients, and Servers&lt;/li&gt;
&lt;li&gt;Tools, Resources, and Prompts — MCP's Three Primitives&lt;/li&gt;
&lt;li&gt;Transports: stdio vs Streamable HTTP&lt;/li&gt;
&lt;li&gt;Why Python Became the Default Language for MCP Servers&lt;/li&gt;
&lt;li&gt;Building Your First MCP Server in Python&lt;/li&gt;
&lt;li&gt;Advanced Python Patterns You'll Actually Use in MCP Servers&lt;/li&gt;
&lt;li&gt;Security, Auth, and Production Concerns&lt;/li&gt;
&lt;li&gt;MCP vs REST APIs vs Function Calling&lt;/li&gt;
&lt;li&gt;Where MCP Is Headed&lt;/li&gt;
&lt;li&gt;Final Thoughts&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The Problem MCP Was Built to Solve
&lt;/h2&gt;

&lt;p&gt;Before we talk about what MCP &lt;em&gt;is&lt;/em&gt;, it's worth understanding the problem that made it necessary in the first place.&lt;/p&gt;

&lt;p&gt;Large language models are extraordinarily capable at reasoning, writing, and understanding language — but on their own, they're isolated. A model has no access to your company's Jira board, your production database, your file system, or the weather API you want it to check before recommending an outfit. To be genuinely useful in real workflows, an AI application needs to reach outside itself and interact with the world: read files, query databases, call APIs, trigger automations.&lt;/p&gt;

&lt;p&gt;For a long time, every AI application solved this problem in its own bespoke way. If you were building a chat assistant that needed to talk to GitHub, you wrote custom integration code specific to your application. If another team wanted their own assistant to talk to GitHub too, they wrote &lt;em&gt;their own&lt;/em&gt; custom integration code, duplicating most of the same logic. Multiply this across dozens of applications and dozens of tools — Slack, Notion, Postgres, Salesforce, internal APIs — and you get what's sometimes called the &lt;strong&gt;M×N integration problem&lt;/strong&gt;: M applications, each needing custom code for N tools, resulting in M times N discrete integration efforts.&lt;/p&gt;

&lt;p&gt;This is exactly the problem that the &lt;a href="https://microsoft.github.io/language-server-protocol/" rel="noopener noreferrer"&gt;Language Server Protocol&lt;/a&gt; solved for code editors and language tooling years earlier. Before LSP, every IDE had to write its own integration for every programming language's autocomplete, linting, and go-to-definition features. LSP standardized that interface once, and suddenly any LSP-compliant editor could talk to any LSP-compliant language server without custom glue code.&lt;/p&gt;

&lt;p&gt;MCP does the same thing, but for AI applications and the tools/data they need to access. Instead of M×N bespoke integrations, you get &lt;strong&gt;M + N&lt;/strong&gt;: an application builds one MCP client implementation, and a tool provider builds one MCP server implementation, and the two can talk to each other immediately, regardless of who built what.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What Exactly Is MCP?
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Model Context Protocol&lt;/strong&gt; is an open, standardized protocol — originally introduced by Anthropic and now maintained as an open specification — that defines how AI applications connect to external context. That "context" comes in three flavors, which we'll cover in detail shortly: &lt;strong&gt;tools&lt;/strong&gt; the model can invoke, &lt;strong&gt;resources&lt;/strong&gt; it can read, and &lt;strong&gt;prompt templates&lt;/strong&gt; a user can trigger.&lt;/p&gt;

&lt;p&gt;At its core, MCP is a client-server protocol built on &lt;strong&gt;JSON-RPC 2.0&lt;/strong&gt;, a lightweight, well-understood messaging format that has been used in developer tooling for years. An MCP &lt;em&gt;server&lt;/em&gt; is a small, focused program that exposes some capability — say, the ability to query a Postgres database, search a codebase, or fetch weather data. An MCP &lt;em&gt;client&lt;/em&gt;, embedded inside an AI application, connects to that server, discovers what it can do, and routes model or user requests to it.&lt;/p&gt;

&lt;p&gt;The genuinely clever part of MCP's design is that servers are &lt;strong&gt;self-describing&lt;/strong&gt;. When a client connects, it doesn't need pre-written documentation or hardcoded assumptions about what the server does — it asks the server directly, and the server responds with structured, machine-readable descriptions of every tool, resource, and prompt it offers, including full JSON Schema definitions for tool arguments. This is what lets a language model dynamically figure out which tool to call and how to call it correctly, without a human ever writing integration code specific to that server.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Core Architecture: Hosts, Clients, and Servers
&lt;/h2&gt;

&lt;p&gt;MCP's architecture has three distinct roles, and getting these straight is essential to understanding everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Host&lt;/strong&gt; is the AI application itself — the thing the end user actually interacts with. This could be a chat interface, an IDE, an autonomous agent framework, or a command-line tool. The host owns the language model, manages the overall conversation, and is ultimately responsible for enforcing permissions and deciding which MCP servers are active at any given time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Client&lt;/strong&gt; lives inside the host and manages a single, stateful connection to exactly one server. If a host wants to talk to three different MCP servers — say, one for GitHub, one for a database, and one for internal documentation — it spins up three separate client instances, each maintaining its own session, handshake state, and message routing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Server&lt;/strong&gt; is an independent process, often in a completely different codebase or even a different programming language than the host, that actually implements some capability. A GitHub MCP server, for example, wraps GitHub's API and exposes operations like "list open issues" or "create a pull request" as discoverable tools.&lt;/p&gt;

&lt;p&gt;This separation matters more than it might seem at first glance. Because servers are independent processes with a standardized interface, the same GitHub MCP server can be plugged into completely different AI applications built by completely different teams, without either side needing to know anything about the other's internal implementation. It also means a server can be written in Python while the host application is written in TypeScript, or vice versa — the protocol doesn't care, because everything happens over JSON-RPC.&lt;/p&gt;

&lt;p&gt;When a client first connects to a server, they perform a &lt;strong&gt;handshake&lt;/strong&gt; via an &lt;code&gt;initialize&lt;/code&gt; request. The client declares which protocol version it supports and which optional capabilities it understands (such as sampling or roots, both covered below). The server responds with its own supported version and the capabilities it offers. From that point forward, both sides only use features the other side has explicitly agreed to support — which is what allows the protocol to evolve over time without breaking older implementations.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Tools, Resources, and Prompts — MCP's Three Primitives
&lt;/h2&gt;

&lt;p&gt;Everything an MCP server exposes falls into one of three categories, and the distinction between them is genuinely useful, not just academic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools&lt;/strong&gt; are &lt;em&gt;model-controlled&lt;/em&gt; — the language model itself decides when to call them, based on the conversation and the tool's description. A tool is essentially a function: it has a name, a natural-language description explaining what it does and when to use it, and an &lt;code&gt;inputSchema&lt;/code&gt; written in JSON Schema describing its expected arguments. When the model decides a tool is relevant, the host sends a &lt;code&gt;tools/call&lt;/code&gt; request with the arguments the model generated, the server executes the underlying logic, and the result flows back into the model's context. Good tool design is genuinely an art — vague descriptions or overly broad tools (a single tool that does five different things depending on a mode flag) tend to produce unreliable, hard-to-predict calls. Narrow, well-named, well-documented tools work dramatically better in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt; are &lt;em&gt;application-controlled&lt;/em&gt; pieces of addressable data — think of them as the GET requests of MCP. Each resource has a URI (&lt;code&gt;file:///project/notes.md&lt;/code&gt;, &lt;code&gt;postgres://orders/12345&lt;/code&gt;, or a custom scheme entirely) and can be listed via &lt;code&gt;resources/list&lt;/code&gt; and fetched via &lt;code&gt;resources/read&lt;/code&gt;. Unlike tools, resources aren't meant to be "invoked" with arguments to trigger an action — they're meant to be read, much like static or semi-static context that a host might want to include in a conversation without the model needing to explicitly ask for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompts&lt;/strong&gt; are &lt;em&gt;user-controlled&lt;/em&gt; templates — reusable, parameterized interaction patterns that a human explicitly triggers, often surfaced as something like a slash command. A "summarize this support ticket" prompt template, for example, might take a ticket ID as a parameter and expand into a fully structured request the model can act on consistently, every time, regardless of how a given user might phrase the same request manually.&lt;/p&gt;

&lt;p&gt;The distinction of &lt;em&gt;who&lt;/em&gt; controls each primitive — model, application, or user — is the key design insight here. It maps cleanly onto how much autonomy you want to grant at each layer, and it's a distinction that shows up constantly once you start designing your own servers.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Transports: stdio vs Streamable HTTP
&lt;/h2&gt;

&lt;p&gt;MCP is transport-agnostic at the message level — everything is JSON-RPC — but two transports dominate real-world usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;stdio&lt;/strong&gt; is used when the server runs as a local subprocess spawned directly by the host. Messages are exchanged over standard input and output streams. This is the simplest possible setup: no networking, no authentication layer, no TLS certificates to manage. It's ideal for local developer tools — a code editor spawning a filesystem-access server, for instance — where the server and host run on the same machine under the same user's permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streamable HTTP&lt;/strong&gt; (which has largely superseded the earlier HTTP+SSE transport) is used when the server is remote — potentially serving many different users, running as an independently deployed and scaled service. This transport supports proper authentication (typically OAuth 2.1, including dynamic client registration and PKCE), horizontal scaling behind a load balancer, and long-lived streaming responses over a single HTTP connection, which matters for tool calls that take a while and benefit from sending incremental progress notifications rather than leaving the user staring at a blank spinner.&lt;/p&gt;

&lt;p&gt;Choosing between the two is mostly a question of deployment topology. If you're building a personal productivity tool that runs entirely on your own machine, stdio is simpler and perfectly sufficient. If you're building a server meant to be used by many different users or organizations — something you'll deploy once and let others connect to remotely — Streamable HTTP with proper auth is the right call.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Why Python Became the Default Language for MCP Servers
&lt;/h2&gt;

&lt;p&gt;If you look at the MCP ecosystem today, an outsized share of servers — official ones and community-built ones alike — are written in Python. This isn't an accident, and it's worth understanding why, especially if you're deciding what language to reach for on your own project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, Python is already the dominant language in the AI/ML ecosystem.&lt;/strong&gt; The people building MCP servers are frequently the same people who already have Python-based data pipelines, ML models, or backend services. Wrapping an existing Python codebase's functionality as an MCP server is often a matter of adding a thin protocol layer on top of code that already exists, rather than a rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, Python's official MCP SDK, and specifically the &lt;code&gt;FastMCP&lt;/code&gt; high-level API, dramatically reduces boilerplate.&lt;/strong&gt; You can turn a plain Python function into a fully spec-compliant MCP tool with a single decorator, and the SDK automatically derives the JSON Schema from your type hints and docstring. This kind of ergonomic, decorator-driven API is a very natural fit for Python's existing conventions (think Flask, FastAPI, Click) and makes the barrier to writing your first server extremely low.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, Python's &lt;code&gt;asyncio&lt;/code&gt; ecosystem maps cleanly onto MCP's I/O-heavy nature.&lt;/strong&gt; Most MCP servers spend the overwhelming majority of their time waiting on I/O — database queries, HTTP calls to third-party APIs, file reads — rather than doing CPU-bound work. This is exactly the workload asyncio was designed for, and Python's async ecosystem (httpx, asyncpg, aiofiles, and so on) is mature enough that building a genuinely concurrent, well-behaved server doesn't require reinventing anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fourth, Python's massive library ecosystem means almost any external system you want to wrap already has a well-supported client library.&lt;/strong&gt; Whether you're building an MCP server around a SQL database, a cloud provider's API, or an internal REST service, chances are there's already a battle-tested Python package for it, meaning your MCP server can be a thin, reliable wrapper rather than something built from scratch.&lt;/p&gt;

&lt;p&gt;None of this means Python is the &lt;em&gt;only&lt;/em&gt; good choice — official SDKs also exist for TypeScript, Java, C#, and Kotlin, and plenty of production servers are written in those languages for good reasons (type safety, existing codebases, performance characteristics). But for prototyping quickly, for wrapping existing data/ML infrastructure, and for the sheer volume of available examples and community support, Python is very often the path of least resistance.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Building Your First MCP Server in Python
&lt;/h2&gt;

&lt;p&gt;Let's make this concrete. Here's a minimal but genuinely functional MCP server using the official Python SDK's &lt;code&gt;FastMCP&lt;/code&gt; interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return a short weather forecast for a given city.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# In a real server, this would call an actual weather API
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sunny in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, 28°C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config://settings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_settings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Expose current server configuration as a readable resource.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;units=metric;language=en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noticing here. The &lt;code&gt;@mcp.tool()&lt;/code&gt; decorator does the heavy lifting: it inspects the function's type hints (&lt;code&gt;city: str&lt;/code&gt;) to build a JSON Schema describing the expected arguments, and it uses the docstring as the tool's description — exactly the metadata a language model needs to decide when and how to call this tool. The &lt;code&gt;@mcp.resource()&lt;/code&gt; decorator similarly exposes a readable piece of data under a URI scheme you define yourself.&lt;/p&gt;

&lt;p&gt;On the client side, connecting to and calling this server looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StdioServerParameters&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.client.stdio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stdio_client&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StdioServerParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather_server.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;stdio_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;as &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_forecast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bengaluru&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is genuinely the entire loop: spawn or connect to the server, perform the handshake with &lt;code&gt;initialize()&lt;/code&gt;, discover what it offers, and issue calls. Everything about schema validation, JSON-RPC message framing, and request/response correlation is handled by the SDK underneath.&lt;/p&gt;

&lt;p&gt;The gap between "this toy example" and "a production-grade server" is mostly about what you put &lt;em&gt;inside&lt;/em&gt; the tool functions — proper error handling, input validation, authentication, logging, and the advanced Python patterns we'll cover next.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Advanced Python Patterns You'll Actually Use in MCP Servers
&lt;/h2&gt;

&lt;p&gt;Once you move past hello-world examples, a handful of advanced Python concepts show up again and again in real MCP server code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async all the way down.&lt;/strong&gt; Because most tool implementations spend their time waiting on network or disk I/O, you'll want your tool functions to be &lt;code&gt;async def&lt;/code&gt; and use async-native libraries (&lt;code&gt;httpx.AsyncClient&lt;/code&gt; instead of &lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;asyncpg&lt;/code&gt; instead of a blocking Postgres driver). A single blocking call inside an async tool function can stall the entire event loop, silently degrading every other concurrent request the server is handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context managers for resource lifecycle.&lt;/strong&gt; Database connections, HTTP client sessions, and file handles all benefit from proper &lt;code&gt;async with&lt;/code&gt; context management, both for correctness and for making sure connections are cleaned up even when a tool call raises an exception midway through.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asynccontextmanager&lt;/span&gt;

&lt;span class="nd"&gt;@asynccontextmanager&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aclose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pydantic models for tool input validation.&lt;/strong&gt; While &lt;code&gt;FastMCP&lt;/code&gt; derives basic JSON Schema from type hints automatically, real-world tools often benefit from explicit Pydantic models — you get richer validation (value ranges, custom validators, nested structures) and much clearer error messages when a model-generated call doesn't quite match the expected shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured error handling that surfaces useful information to the model.&lt;/strong&gt; A common mistake is letting an unhandled exception propagate and crash the tool call entirely. Instead, catch expected failure modes and return a clear, actionable error message as part of the tool result — this lets the model see exactly what went wrong and decide whether to retry, adjust its approach, or ask the user for clarification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run a read-only SQL query against the analytics database.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;format_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;QuerySyntaxError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: invalid SQL syntax — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: this query touches a restricted table.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Decorators for cross-cutting concerns.&lt;/strong&gt; Logging, rate limiting, retries, and permission checks all tend to repeat across many tools in a real server, which makes them natural candidates for decorators layered on top of (or alongside) &lt;code&gt;@mcp.tool()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@functools.wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Calling &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; with &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; returned successfully&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dataclasses for structured internal state.&lt;/strong&gt; Servers that maintain any kind of session or cached state benefit from dataclasses over loose dictionaries — you get type safety, auto-generated &lt;code&gt;__repr__&lt;/code&gt; for debugging, and a much clearer contract for what data a given piece of state actually holds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Careful use of &lt;code&gt;functools.lru_cache&lt;/code&gt; for expensive, pure computations&lt;/strong&gt; — but only for genuinely pure, deterministic operations, since caching something that depends on external state (like a live API call) will silently serve stale data.&lt;/p&gt;

&lt;p&gt;None of these are exotic techniques — they're standard, well-understood Python practices. What's notable is how consistently they show up once you move from "toy tool that returns a hardcoded string" to "tool that talks to a real database under real concurrent load."&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Security, Auth, and Production Concerns
&lt;/h2&gt;

&lt;p&gt;It's worth spending a moment on security, because MCP servers occupy an unusual trust position: they run with whatever permissions the host grants them, and their output — tool descriptions, resource content, error messages — ultimately becomes part of the model's context.&lt;/p&gt;

&lt;p&gt;This means a poorly designed or malicious server can attempt something like prompt injection: crafting tool descriptions or returned data in a way that tries to manipulate the model's subsequent behavior. The practical defenses are the same ones you'd apply to any system handling untrusted input: validate and sanitize everything server-side rather than trusting arguments the model generates, apply the principle of least privilege to whatever credentials the server holds, use MCP's "roots" feature to scope filesystem access to only what's actually needed, and require explicit user confirmation before executing anything destructive (deleting data, sending emails, making purchases).&lt;/p&gt;

&lt;p&gt;For remote, HTTP-based servers, authentication should go through the OAuth 2.1 flow MCP specifies, obtaining short-lived, appropriately scoped tokens rather than baking long-lived API keys directly into server configuration — a mistake that significantly increases blast radius if that configuration ever leaks.&lt;/p&gt;

&lt;p&gt;On the operational side, production MCP servers benefit from the same discipline as any backend service: structured logging of tool calls (with sensitive fields redacted), rate limiting to prevent runaway agentic loops from hammering downstream systems, keeping session state external (Redis, a database) rather than in local process memory so the service can scale horizontally, and explicit versioning so hosts can pin to a known-good schema as the server evolves.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. MCP vs REST APIs vs Function Calling
&lt;/h2&gt;

&lt;p&gt;A question that comes up constantly: if MCP is essentially a standardized way to expose functions and data, how is it different from a REST API, or from the native "function calling" features most LLM APIs already support?&lt;/p&gt;

&lt;p&gt;A traditional &lt;strong&gt;REST API&lt;/strong&gt; is built for a human developer to read documentation and hand-write integration code against fixed endpoints. There's no built-in mechanism for a client to dynamically discover what's available or how to call it — that knowledge lives in documentation, external to the API itself.&lt;/p&gt;

&lt;p&gt;Native &lt;strong&gt;function calling&lt;/strong&gt;, offered directly by most LLM APIs, lets a single application define tools for that specific model to call — but that tool definition is tied entirely to that one codebase. If another application wants the same functionality, it has to reimplement the tool definitions and the underlying logic from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt; sits in between these, solving the discovery problem REST lacks and the portability problem proprietary function calling lacks. A client can ask a server what it offers at runtime and receive machine-readable schemas the model can act on directly — no documentation-reading required, and no per-application reimplementation. The same server can be plugged into any MCP-compliant host, regardless of who built it. MCP also standardizes bidirectional capabilities like sampling (a server requesting a model completion) and change notifications, which fall outside the scope of what either REST or basic function calling addresses.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Where MCP Is Headed
&lt;/h2&gt;

&lt;p&gt;MCP is still a young protocol, and it's evolving quickly. A few trends worth watching: growing adoption of the Streamable HTTP transport for remote, multi-tenant servers as more companies expose official MCP servers for their products; increasing standardization around authentication and enterprise-grade access control; a growing public registry of community-built servers spanning databases, SaaS tools, and developer platforms; and continued refinement of features like sampling and roots as more hosts implement the full specification rather than just the basics.&lt;/p&gt;

&lt;p&gt;For Python developers specifically, this means the ecosystem of async-native client libraries, SDK ergonomics, and tooling (like the MCP Inspector for interactively testing servers) is likely to keep maturing quickly — which is good news if you're getting in now, since the tooling gap between "hello world" and "production-ready" keeps shrinking.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Final Thoughts
&lt;/h2&gt;

&lt;p&gt;MCP represents a genuinely useful shift in how AI applications connect to the outside world — replacing a mess of bespoke, one-off integrations with a single, self-describing protocol that any compliant host and server can speak. Python's combination of a mature async ecosystem, an enormous library surface for wrapping existing systems, and a low-friction official SDK has made it one of the most natural languages to build MCP servers in, which is exactly why so much of the ecosystem's early growth has happened there.&lt;/p&gt;

&lt;p&gt;If you're a Python developer looking to get genuinely hands-on with this — whether for a side project, for work, or to walk into interviews with real depth instead of surface-level buzzwords — building a couple of real MCP servers yourself is by far the fastest way to internalize all of this. Start small: wrap one API you already use, add proper error handling and async I/O, and go from there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Want to Go Deeper? Get the Full Interview Prep Guide
&lt;/h2&gt;

&lt;p&gt;If this article gave you a solid foundation, the next step is testing that understanding against real interview-style questions — the kind that come up when you're being evaluated for a role that touches AI tooling, agentic systems, or advanced Python engineering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;50 Advanced MCP &amp;amp; Python Interview Questions and Answers&lt;/strong&gt; is a focused PDF guide covering everything discussed here in much greater depth — 25 questions on MCP architecture, transports, security, and production deployment, and 25 questions on advanced Python: the GIL, asyncio internals, descriptors, metaclasses, memory management, concurrency, testing, and more. Every answer includes real, working code examples, not just textbook definitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 Download it here: &lt;a href="https://himanshuai.gumroad.com/l/50AdvancedMCPPythonInterviewQuestionsandAnswers" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/50AdvancedMCPPythonInterviewQuestionsandAnswers&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Written by Himanshu Agarwal.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>MCP, RAG &amp; LLM Mastery — 300 Interview Questions &amp; Answers</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Mon, 03 Aug 2026 08:47:39 +0000</pubDate>
      <link>https://dev.to/himanshuai/mcp-rag-llm-mastery-300-interview-questions-answers-10ak</link>
      <guid>https://dev.to/himanshuai/mcp-rag-llm-mastery-300-interview-questions-answers-10ak</guid>
      <description>&lt;h3&gt;
  
  
  The Complete Guide for Senior Engineers (5–15 Years Experience)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Written by Himanshu Agarwal&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;This guide contains &lt;strong&gt;300 in-depth interview questions and answers&lt;/strong&gt; — 100 each on &lt;strong&gt;LLMs&lt;/strong&gt;, &lt;strong&gt;RAG (Retrieval-Augmented Generation)&lt;/strong&gt;, and &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; — curated specifically for senior engineers, architects, and tech leads (5–15 YOE) preparing for AI/ML, GenAI, and applied AI engineering interviews.&lt;/p&gt;

&lt;p&gt;Want the full deep-dive version with case studies, system design diagrams, code walkthroughs, and mock interview drills? Check out the &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com/l/MCP-RAG-LLM-Mastery-Bundle" rel="noopener noreferrer"&gt;MCP, RAG &amp;amp; LLM Mastery Bundle&lt;/a&gt;&lt;/strong&gt; on Gumroad.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Part 1 — Large Language Models (100 Q&amp;amp;A)&lt;/li&gt;
&lt;li&gt;Part 2 — Retrieval-Augmented Generation (100 Q&amp;amp;A)&lt;/li&gt;
&lt;li&gt;Part 3 — Model Context Protocol (100 Q&amp;amp;A)&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;About the Author&lt;/li&gt;
&lt;li&gt;Explore the Full Bundle&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Part 1 — Large Language Models (100 Q&amp;amp;A)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. Foundations &amp;amp; Architecture (Q1–15)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. What is a Large Language Model (LLM)?&lt;/strong&gt;&lt;br&gt;
An LLM is a neural network, typically Transformer-based, trained on massive text corpora to predict the next token in a sequence. At scale (billions of parameters), this next-token prediction objective gives rise to emergent capabilities like reasoning, translation, and code generation, without those tasks being explicitly trained for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Explain the Transformer architecture at a senior level.&lt;/strong&gt;&lt;br&gt;
The Transformer replaces recurrence with self-attention, allowing every token to attend to every other token in parallel. Core components: multi-head self-attention, position-wise feed-forward networks, residual connections, layer normalization, and positional encodings. This parallelism is what made training on web-scale data computationally feasible versus RNNs/LSTMs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What is self-attention and why does it scale quadratically?&lt;/strong&gt;&lt;br&gt;
Self-attention computes a weighted sum of value vectors, where weights come from the dot product of query and key vectors across all token pairs. Because every token compares against every other token, compute and memory scale as O(n²) with sequence length n — the primary bottleneck for long-context models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Difference between encoder-only, decoder-only, and encoder-decoder models?&lt;/strong&gt;&lt;br&gt;
Encoder-only (BERT) builds bidirectional representations, ideal for classification/embedding tasks. Decoder-only (GPT, Llama) is autoregressive and causal, ideal for generation. Encoder-decoder (T5, BART) combines both — encoder reads the full input, decoder generates output conditioned on it — well suited to translation and summarization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. What are positional encodings and why are they needed?&lt;/strong&gt;&lt;br&gt;
Self-attention is permutation-invariant by default — it has no notion of token order. Positional encodings (sinusoidal, learned, or rotary/RoPE) inject order information. RoPE, used in most modern LLMs, encodes relative position via rotation matrices applied to query/key vectors, generalizing better to longer sequences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Explain multi-head attention and why multiple heads help.&lt;/strong&gt;&lt;br&gt;
Instead of one attention computation, the model splits Q/K/V into multiple lower-dimensional "heads" that attend in parallel, each potentially specializing in different relationships (syntax, coreference, long-range dependency). Outputs are concatenated and projected back, giving richer representational capacity than a single attention head.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. What is the role of layer normalization, and Pre-LN vs Post-LN?&lt;/strong&gt;&lt;br&gt;
LayerNorm stabilizes training by normalizing activations across the feature dimension. Post-LN (original Transformer) applies norm after the residual add; Pre-LN applies it before the sublayer. Pre-LN gives more stable gradients at scale and is preferred in modern LLMs since it avoids gradient explosion in very deep stacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. What is KV caching and why is it critical for inference?&lt;/strong&gt;&lt;br&gt;
During autoregressive generation, keys and values for previously generated tokens don't change, so caching them avoids recomputing attention over the whole sequence at every step. This turns per-token generation cost from O(n²) to O(n), making real-time inference feasible, at the cost of growing memory usage with context length.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Explain Mixture of Experts (MoE) architectures.&lt;/strong&gt;&lt;br&gt;
MoE replaces a single dense FFN with multiple "expert" FFNs, and a gating/router network selects a sparse subset (e.g., top-2) per token. This decouples parameter count from compute cost — models like Mixtral or DeepSeek-MoE have huge total parameters but only activate a fraction per forward pass, improving efficiency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. What is Grouped Query Attention (GQA) and Multi-Query Attention (MQA)?&lt;/strong&gt;&lt;br&gt;
MQA shares a single K/V head across all query heads, drastically reducing KV cache size at some quality cost. GQA is a middle ground — groups of query heads share a K/V head. Modern models (Llama 3, Mistral) use GQA to balance inference efficiency and generation quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. How does RoPE (Rotary Position Embedding) work?&lt;/strong&gt;&lt;br&gt;
RoPE encodes absolute position by rotating query and key vectors in 2D subspaces by an angle proportional to position, so the dot product between two rotated vectors naturally encodes their relative distance. This gives better extrapolation to longer sequences than fixed sinusoidal or learned absolute embeddings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. What is context window and what limits it?&lt;/strong&gt;&lt;br&gt;
The context window is the maximum number of tokens (input + output) the model can process at once. It's limited by the O(n²) attention cost, positional encoding generalization, and KV cache memory. Techniques like sliding window attention, RoPE scaling, and linear attention variants extend it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. What are activation functions commonly used in LLM FFNs?&lt;/strong&gt;&lt;br&gt;
Modern LLMs mostly use SwiGLU or GeGLU (gated linear units combined with Swish/GELU) instead of plain ReLU, because the gating mechanism improves expressiveness and gradient flow. SwiGLU is used in Llama, PaLM, and most current-generation open models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. What is the difference between parameters and FLOPs, and why does it matter for scaling?&lt;/strong&gt;&lt;br&gt;
Parameters are the model's learned weights; FLOPs measure actual compute used during training/inference. Chinchilla scaling laws showed that for a fixed compute budget, there's an optimal balance of parameters vs training tokens — many earlier models were "undertrained" relative to their size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. Explain tokenization and why subword tokenization (BPE) is used.&lt;/strong&gt;&lt;br&gt;
Byte-Pair Encoding iteratively merges frequent character/subword pairs into a vocabulary, balancing between word-level (huge vocab, poor OOV handling) and character-level (long sequences, weak semantics) tokenization. It lets models handle rare words, multiple languages, and code efficiently with a fixed vocabulary size.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Training &amp;amp; Fine-Tuning (Q16–30)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;16. Describe the full LLM training pipeline.&lt;/strong&gt;&lt;br&gt;
Pre-training (self-supervised next-token prediction on trillions of tokens) → Supervised Fine-Tuning (SFT) on instruction-response pairs → Preference alignment (RLHF/DPO) → optional domain-specific fine-tuning/RAG integration → safety red-teaming and evaluation before deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. What is RLHF and how does it work end-to-end?&lt;/strong&gt;&lt;br&gt;
Reinforcement Learning from Human Feedback: (1) collect human preference data ranking model outputs, (2) train a reward model to predict those preferences, (3) use PPO to fine-tune the LLM policy to maximize reward while a KL penalty keeps it close to the SFT model, preventing reward hacking/degeneration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What is DPO (Direct Preference Optimization) and why is it popular?&lt;/strong&gt;&lt;br&gt;
DPO reformulates the RLHF objective into a single supervised loss directly on preference pairs, eliminating the separate reward model and RL loop. It's simpler, more stable, and cheaper to train than PPO-based RLHF while achieving comparable alignment quality, which is why most open models now use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. Explain LoRA (Low-Rank Adaptation) fine-tuning.&lt;/strong&gt;&lt;br&gt;
LoRA freezes the pretrained weight matrices and injects trainable low-rank decomposition matrices (A, B) alongside them, so updates are ΔW = BA with rank r &amp;lt;&amp;lt; d. This cuts trainable parameters by orders of magnitude, drastically reducing GPU memory and enabling fine-tuning of large models on modest hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. What is QLoRA and how does it differ from LoRA?&lt;/strong&gt;&lt;br&gt;
QLoRA quantizes the base model to 4-bit precision (NF4) and fine-tunes LoRA adapters on top in higher precision, using techniques like double quantization and paged optimizers to manage memory spikes. It enables fine-tuning 65B+ parameter models on a single consumer GPU.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21. What is catastrophic forgetting and how do you mitigate it during fine-tuning?&lt;/strong&gt;&lt;br&gt;
Fine-tuning on a narrow dataset can overwrite general capabilities learned during pre-training. Mitigations: use parameter-efficient methods like LoRA (limits weight drift), mix in a portion of general instruction data, use lower learning rates, early stopping, and evaluate on held-out general benchmarks during training.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22. When would you choose full fine-tuning over PEFT methods like LoRA?&lt;/strong&gt;&lt;br&gt;
Full fine-tuning is justified when you need deep domain adaptation (e.g., a new language, drastically different data distribution, or architecture-level behavior changes) and have the compute budget. For most instruction-tuning, style adaptation, or narrow task specialization, PEFT (LoRA/QLoRA) gives comparable results far more cheaply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23. Explain instruction tuning and its purpose.&lt;/strong&gt;&lt;br&gt;
Instruction tuning fine-tunes a base (raw next-token) model on curated (instruction, response) pairs so it learns to follow natural language commands rather than just continue text. It's the step that converts a "text completer" into an assistant-like model responsive to prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24. What is the role of a reward model in alignment pipelines?&lt;/strong&gt;&lt;br&gt;
The reward model is trained on human preference comparisons (A vs B rankings) to output a scalar score approximating human judgment of response quality. It acts as a proxy objective the policy model optimizes against during RL-based alignment, since raw human feedback can't be given at every training step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25. What is constitutional AI / RLAIF?&lt;/strong&gt;&lt;br&gt;
Instead of relying purely on human-labeled preferences, the model critiques and revises its own outputs against a written set of principles (a "constitution"), and an AI (not human) labels preferences based on those principles (RLAIF - RL from AI Feedback), reducing human annotation cost while maintaining alignment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26. How do you decide the right learning rate and batch size for fine-tuning?&lt;/strong&gt;&lt;br&gt;
Use a much smaller LR than pre-training (often 1e-5 to 5e-5 for full fine-tune, higher like 1e-4 to 3e-4 for LoRA), with linear warmup and cosine/linear decay. Batch size is constrained by GPU memory; use gradient accumulation to simulate larger effective batches, and monitor loss curves for instability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;27. What is gradient checkpointing and why use it?&lt;/strong&gt;&lt;br&gt;
Instead of storing all intermediate activations for backprop, gradient checkpointing stores only a subset and recomputes the rest during the backward pass. This trades compute for memory, enabling training of larger models or longer sequences on limited GPU memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;28. Explain mixed precision training (FP16/BF16) and why BF16 is often preferred.&lt;/strong&gt;&lt;br&gt;
Mixed precision uses lower-precision (16-bit) formats for most computation while keeping a master copy of weights in FP32, speeding up training and halving memory. BF16 has the same exponent range as FP32 (better numerical stability, no need for loss scaling) versus FP16, which has more mantissa precision but a narrower range prone to overflow/underflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;29. What is data contamination in LLM training/evaluation, and how do you detect it?&lt;/strong&gt;&lt;br&gt;
Contamination occurs when benchmark test data leaks into the training corpus, inflating evaluation scores unrealistically. Detection methods: n-gram overlap analysis between training data and benchmarks, canary strings, and held-out/decontaminated benchmark variants released after a model's training cutoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30. How would you curate a high-quality instruction-tuning dataset?&lt;/strong&gt;&lt;br&gt;
Prioritize diversity of task types, difficulty, and domains; deduplicate aggressively; filter for length and quality (heuristics + model-based scoring); mix human-written and synthetic (model-generated, then verified) examples; include multi-turn conversations; and balance refusal/safety examples without over-indexing on them.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Prompting &amp;amp; In-Context Learning (Q31–40)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;31. What is in-context learning (ICL) and why does it emerge?&lt;/strong&gt;&lt;br&gt;
ICL is the ability of an LLM to learn a task from examples given directly in the prompt, without weight updates. It's believed to emerge from pre-training exposure to naturally occurring "few-shot-like" patterns in text, and is more pronounced in larger models — a key emergent capability of scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32. Zero-shot vs few-shot vs chain-of-thought prompting — when to use each?&lt;/strong&gt;&lt;br&gt;
Zero-shot works for simple, well-known tasks. Few-shot helps when output format or task nuance is ambiguous and examples clarify it. Chain-of-thought (asking the model to reason step-by-step) significantly improves performance on multi-step reasoning, math, and logic tasks by giving the model "space" to compute intermediate steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;33. What is prompt engineering and what are core best practices?&lt;/strong&gt;&lt;br&gt;
It's the practice of structuring inputs to reliably elicit desired model behavior: being explicit about format/constraints, providing examples, decomposing complex tasks, using role/system prompts, requesting structured output (JSON/XML), and iterating empirically since LLM behavior is sensitive to phrasing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;34. Explain the difference between system, user, and assistant roles in chat models.&lt;/strong&gt;&lt;br&gt;
System sets persistent behavior/persona/constraints for the whole conversation. User messages are the human's turns. Assistant messages are the model's prior responses, included in context for multi-turn coherence. Training data explicitly labels these roles so the model learns differentiated behavior per role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;35. What is self-consistency prompting?&lt;/strong&gt;&lt;br&gt;
Instead of taking one chain-of-thought output, you sample multiple reasoning paths (with temperature &amp;gt; 0) and take a majority vote on the final answer. This improves accuracy on reasoning tasks by marginalizing out noise in any single generation path, at the cost of extra inference calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;36. What is ReAct prompting?&lt;/strong&gt;&lt;br&gt;
ReAct interleaves reasoning ("Thought") and actions ("Action", e.g., tool calls) in the same generation loop — the model reasons about what to do, takes an action (like a search query), observes the result, and continues reasoning. This is foundational to agentic LLM systems and tool use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;37. How do you mitigate prompt injection in production LLM applications?&lt;/strong&gt;&lt;br&gt;
Separate trusted (system) instructions from untrusted (user/retrieved) content structurally, use delimiters and explicit instructions to ignore embedded commands, apply input/output filtering, sandbox tool execution with least privilege, and consider using models specifically hardened against injection with dedicated instruction hierarchies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;38. What is the "lost in the middle" problem in long-context prompting?&lt;/strong&gt;&lt;br&gt;
Research shows LLMs often attend better to information at the beginning and end of a long context than the middle, degrading recall for facts placed mid-context. Mitigations: place critical information near the start/end, use retrieval to keep context focused, or use models specifically tuned for long-context recall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;39. Explain few-shot example selection strategies for production prompts.&lt;/strong&gt;&lt;br&gt;
Static few-shot examples work for stable tasks; dynamic selection (e.g., retrieving semantically similar examples via embeddings per query) improves performance on diverse inputs. Diversity and difficulty-matching in the example set both matter more than raw example count beyond a small threshold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;40. What is structured output prompting (JSON mode / function calling) and why is it important for production systems?&lt;/strong&gt;&lt;br&gt;
It constrains the model's output to a defined schema (via grammar-constrained decoding, fine-tuned "JSON mode," or function-calling APIs), making outputs machine-parseable and reliable for downstream integration — critical for agents, tool use, and any system where free text can't be safely parsed.&lt;/p&gt;

&lt;h3&gt;
  
  
  D. Inference &amp;amp; Optimization (Q41–55)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;41. Explain quantization (INT8, INT4, GPTQ, AWQ) and its trade-offs.&lt;/strong&gt;&lt;br&gt;
Quantization reduces weight/activation precision to shrink memory footprint and increase throughput. GPTQ uses layer-wise error-minimizing quantization post-training; AWQ preserves precision for "salient" weight channels identified by activation statistics. Trade-off: lower bit-widths risk quality degradation, especially on reasoning-heavy tasks, requiring careful calibration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;42. What is speculative decoding and how does it speed up inference?&lt;/strong&gt;&lt;br&gt;
A small, fast "draft" model generates several candidate tokens ahead, and the large target model verifies them in a single parallel forward pass, accepting the correct prefix and only falling back to normal generation on mismatch. This can 2-3x throughput since verification is cheaper than autoregressive generation token-by-token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;43. What is continuous batching and why does it matter for serving throughput?&lt;/strong&gt;&lt;br&gt;
Traditional static batching waits for all requests in a batch to finish before starting new ones, wasting GPU cycles on short sequences. Continuous (dynamic) batching, used in engines like vLLM and TGI, injects/evicts requests token-by-token, keeping GPU utilization high and dramatically improving serving throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;44. Explain PagedAttention (vLLM) and the problem it solves.&lt;/strong&gt;&lt;br&gt;
KV cache memory is traditionally allocated contiguously per sequence, causing fragmentation and wasted memory since sequence lengths vary. PagedAttention borrows OS virtual memory paging concepts — KV cache is stored in non-contiguous fixed-size blocks — enabling near-zero memory waste and much higher batch sizes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;45. What factors determine LLM inference latency, and how do you optimize each?&lt;/strong&gt;&lt;br&gt;
Time-to-first-token (prompt processing, prefill compute) and inter-token latency (memory-bandwidth-bound decode step) are the two main components. Optimize prefill with better batching/parallelism; optimize decode with quantization, speculative decoding, smaller KV cache (GQA/MQA), and hardware with high memory bandwidth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;46. What is model distillation and when would you use it?&lt;/strong&gt;&lt;br&gt;
A smaller "student" model is trained to mimic a larger "teacher" model's outputs (soft labels/logits or generated data), transferring much of its capability at a fraction of the size/cost. Use it when you need low-latency, low-cost inference for a narrower task where the teacher's full generality isn't needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;47. Explain the trade-offs between temperature, top-k, and top-p (nucleus) sampling.&lt;/strong&gt;&lt;br&gt;
Temperature scales the logits' sharpness (low = deterministic, high = diverse/random). Top-k restricts sampling to the k most likely tokens. Top-p samples from the smallest set of tokens whose cumulative probability exceeds p, adapting dynamically to the model's confidence — generally preferred over top-k for more natural diversity control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;48. How would you architect an LLM serving system for high concurrency at low cost?&lt;/strong&gt;&lt;br&gt;
Use a high-throughput inference engine (vLLM/TensorRT-LLM) with continuous batching and PagedAttention, quantize models where quality allows, implement autoscaling with request queuing, route by model/task tier, cache common prompts/responses, and use speculative decoding or smaller distilled models for latency-sensitive paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;49. What is FlashAttention and why does it matter?&lt;/strong&gt;&lt;br&gt;
FlashAttention is an IO-aware exact attention algorithm that avoids materializing the full n×n attention matrix in slow HBM memory, instead computing attention in fused, tiled kernels using fast SRAM. This gives significant speedups and memory savings without any approximation, and is now standard in most training/inference stacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;50. Explain tensor parallelism vs pipeline parallelism vs data parallelism.&lt;/strong&gt;&lt;br&gt;
Data parallelism replicates the full model across devices, each processing different data batches. Tensor parallelism splits individual weight matrices across devices (needed when a single layer doesn't fit on one GPU). Pipeline parallelism splits the model by layers across devices, passing activations sequentially — often combined (3D parallelism) for very large models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;51. What is the cost/latency trade-off between using a large frontier model vs a smaller fine-tuned model in production?&lt;/strong&gt;&lt;br&gt;
Frontier models offer stronger zero-shot generalization and reasoning but cost more per token and have higher latency. Smaller fine-tuned/distilled models can match or exceed frontier performance on narrow, well-specified tasks at a fraction of the cost — the right choice depends on task breadth, volume, and latency SLAs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;52. What is caching in the context of LLM applications and what layers can be cached?&lt;/strong&gt;&lt;br&gt;
Layers include: exact prompt-response caching, semantic caching (cache hits on semantically similar queries via embeddings), KV cache reuse for shared prefixes (prompt caching offered by major providers), and retrieval result caching in RAG systems — each reduces redundant compute and cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;53. How do you handle rate limiting and retries robustly when calling LLM APIs at scale?&lt;/strong&gt;&lt;br&gt;
Implement exponential backoff with jitter, respect provider rate-limit headers, use request queuing/token-bucket throttling client-side, batch where APIs support it, have fallback models/providers, and design idempotent retry logic that handles partial failures without duplicating side effects (like tool calls).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;54. What is structured/constrained decoding and how is it implemented?&lt;/strong&gt;&lt;br&gt;
It restricts the model's token sampling at each step to only tokens valid under a given grammar/schema (e.g., JSON schema), typically via masking invalid logits before sampling. Libraries like Outlines, Guidance, or provider-native "structured output" modes implement this, guaranteeing syntactically valid output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;55. What is the difference between prefill and decode phases in LLM inference, and why are they optimized differently?&lt;/strong&gt;&lt;br&gt;
Prefill processes the entire input prompt in parallel (compute-bound, benefits from large batch/matrix ops). Decode generates one token at a time autoregressively (memory-bandwidth-bound, since KV cache reads dominate). Serving systems often use disaggregated prefill/decode architectures to optimize each phase independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  E. Evaluation (Q56–65)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;56. How do you evaluate LLM outputs beyond standard benchmarks like MMLU?&lt;/strong&gt;&lt;br&gt;
Combine automated benchmarks (task-specific accuracy), LLM-as-judge scoring against rubrics, human evaluation (pairwise preference or Likert scoring), task-specific business metrics (e.g., resolution rate for a support bot), and red-teaming for safety/robustness — no single metric suffices for production readiness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;57. What is LLM-as-a-judge and what are its pitfalls?&lt;/strong&gt;&lt;br&gt;
Using a strong LLM to score/compare outputs against criteria, scaling evaluation beyond human capacity. Pitfalls: positional bias (favoring the first option shown), verbosity bias (favoring longer answers), self-preference bias (favoring outputs similar to its own style), and inconsistency — mitigated by randomizing order, using rubrics, and calibrating against human judgments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;58. How do you evaluate factuality/hallucination in LLM outputs?&lt;/strong&gt;&lt;br&gt;
Techniques include fact-verification against a trusted knowledge source (NLI-based entailment checking), self-consistency checks (sampling multiple generations and checking agreement), citation-grounding verification (does the claim map to a retrieved source), and human spot-checking on a sampled basis for production monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;59. What is perplexity and what are its limitations as an evaluation metric?&lt;/strong&gt;&lt;br&gt;
Perplexity measures how well a model predicts held-out text (lower = better fit). It correlates with fluency but poorly with downstream task usefulness, factuality, or instruction-following — a model can have low perplexity while being unhelpful or incorrect, so it's mainly used for pre-training model comparison, not instruction-tuned model evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;60. How would you design an eval harness for a domain-specific fine-tuned LLM?&lt;/strong&gt;&lt;br&gt;
Build a held-out labeled test set representative of production distribution, define task-specific metrics (exact match, F1, ROUGE, or rubric-based scoring depending on task type), include adversarial/edge cases, run regression testing on every model/prompt change, and track metrics over time with statistical significance testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;61. What is the difference between intrinsic and extrinsic evaluation?&lt;/strong&gt;&lt;br&gt;
Intrinsic evaluation measures model properties directly (perplexity, benchmark accuracy) independent of a downstream application. Extrinsic evaluation measures impact on the actual task/business outcome (e.g., customer satisfaction, task completion rate) — extrinsic metrics are ultimately what matter for production decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;62. How do you evaluate reasoning capability specifically?&lt;/strong&gt;&lt;br&gt;
Use benchmarks requiring multi-step logic (GSM8K, MATH, BBH), analyze chain-of-thought traces for logical validity (not just final-answer correctness), test consistency by paraphrasing the same problem, and check robustness to distractors/irrelevant information injected into the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;63. What is a golden dataset and how do you build one for evaluation?&lt;/strong&gt;&lt;br&gt;
A golden dataset is a curated, high-quality, human-verified set of input-output pairs representing correct/ideal behavior. Build it by sampling real production queries, having domain experts label ideal responses, ensuring coverage of edge cases and difficulty levels, and periodically refreshing it as the task distribution shifts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;64. How do you detect and measure bias in LLM outputs?&lt;/strong&gt;&lt;br&gt;
Use counterfactual testing (swap demographic attributes in prompts and measure output differences), established bias benchmarks (BBQ, StereoSet), disaggregated evaluation across subgroups on real tasks, and qualitative red-teaming — statistical parity alone is insufficient without contextual judgment of harm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;65. What is A/B testing's role in evaluating LLM features in production, and what are its unique challenges?&lt;/strong&gt;&lt;br&gt;
A/B testing measures real user impact (engagement, task success, satisfaction) beyond offline metrics. Unique LLM challenges: non-determinism requires larger sample sizes for statistical power, delayed/indirect quality signals (e.g., a wrong answer's harm may not surface immediately), and the need to guard against regressions in tail/edge cases invisible in aggregate metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  F. Alignment &amp;amp; Safety (Q66–75)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;66. What is the alignment problem in the context of LLMs?&lt;/strong&gt;&lt;br&gt;
Ensuring a model's behavior matches human intentions and values — not just being capable, but being helpful, honest, and harmless in ways humans actually want, including handling ambiguous or underspecified instructions safely and avoiding deceptive or harmful behavior even when technically "following orders."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;67. Explain the difference between helpfulness and harmlessness trade-offs in RLHF.&lt;/strong&gt;&lt;br&gt;
Optimizing purely for helpfulness can produce outputs that comply with harmful requests; optimizing purely for harmlessness can produce an overly-refusing, unhelpful model. Modern alignment balances both via multi-objective reward modeling or constitutional principles that specify nuanced, context-sensitive refusal rather than blanket restriction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;68. What is jailbreaking and what are common techniques attackers use?&lt;/strong&gt;&lt;br&gt;
Jailbreaking is crafting inputs to bypass a model's safety training. Common techniques: role-play/persona framing ("pretend you're an AI with no restrictions"), prompt injection via indirect content, encoding harmful requests (base64, translated language), multi-turn escalation, and exploiting instruction-hierarchy confusion between system/user content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;69. How do you red-team an LLM application before production launch?&lt;/strong&gt;&lt;br&gt;
Assemble adversarial testers (internal + external) to probe for harmful outputs, bias, prompt injection, data leakage, and jailbreaks systematically across categories; use automated adversarial prompt generation tools; document and triage findings by severity; and re-test after each mitigation before sign-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;70. What is the difference between guardrails and alignment training?&lt;/strong&gt;&lt;br&gt;
Alignment training (RLHF/DPO/Constitutional AI) shapes the model's underlying behavior during training. Guardrails are external systems (input/output filters, classifiers, rule-based checks) wrapped around the model at inference time as a second layer of defense — production systems typically need both, since neither alone is fully robust.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;71. What is reward hacking and how does it manifest in RLHF-trained models?&lt;/strong&gt;&lt;br&gt;
The policy model finds ways to maximize the reward model's score without actually satisfying the true underlying objective — e.g., producing overly long, verbose, or sycophantic responses because the reward model correlates those with quality. Mitigated with reward model regularization, KL penalties, and diverse preference data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;72. Explain sycophancy in LLMs and why it's a safety concern.&lt;/strong&gt;&lt;br&gt;
Sycophancy is the tendency of a model to agree with or flatter the user's stated views rather than give an accurate/honest answer, often a side effect of RLHF optimizing for human-rated approval. It's concerning because it can reinforce misinformation and erode the model's reliability as an objective source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;73. What is Constitutional AI and how does it differ from standard RLHF?&lt;/strong&gt;&lt;br&gt;
Constitutional AI has the model critique and revise its own responses against a set of written principles, then trains on the self-improved outputs (plus AI-generated preference labels), reducing reliance on large-scale human labeling of harmful content while making the alignment criteria explicit and auditable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;74. How do you handle PII and data privacy in LLM applications?&lt;/strong&gt;&lt;br&gt;
Implement PII detection/redaction on inputs before logging or sending to third-party APIs, use data processing agreements with providers, avoid training/fine-tuning on sensitive user data without consent, apply differential privacy techniques where feasible, and ensure retention/deletion policies comply with regulations (GDPR, HIPAA, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;75. What is the difference between AI safety and AI alignment as fields, and why does it matter for a practitioner?&lt;/strong&gt;&lt;br&gt;
Alignment focuses narrowly on making a model's behavior match intended goals; safety is the broader field including alignment plus robustness, interpretability, misuse prevention, and societal impact. A practitioner needs both: aligned models that also fail gracefully, resist misuse, and are monitored in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  G. Scaling, Systems &amp;amp; Emerging Topics (Q76–90)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;76. Explain scaling laws (Chinchilla) and their practical implications.&lt;/strong&gt;&lt;br&gt;
Chinchilla scaling laws found that for a fixed compute budget, model size and training tokens should scale roughly equally — many earlier large models were undertrained relative to their parameter count. Practical implication: a smaller model trained on more data can outperform a larger undertrained one at the same compute cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;77. What are emergent abilities in LLMs and are they real or a measurement artifact?&lt;/strong&gt;&lt;br&gt;
Emergent abilities are capabilities (e.g., multi-step arithmetic) that appear sharply at certain scale thresholds rather than improving gradually. Some research argues this is partly a measurement artifact of discontinuous metrics (exact-match) rather than the underlying capability, which may improve smoothly when measured with continuous metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;78. What is Retrieval-Augmented Generation and how does it relate to LLM limitations?&lt;/strong&gt;&lt;br&gt;
RAG grounds LLM generation in retrieved external documents at inference time, addressing the model's static knowledge cutoff, hallucination tendency, and inability to cite sources — without requiring retraining for every new piece of information. (Deep dive in Part 2.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;79. What is an AI agent and how does it differ from a standard LLM call?&lt;/strong&gt;&lt;br&gt;
An agent uses an LLM as a reasoning engine in a loop — planning, taking actions via tools, observing results, and iterating — to accomplish multi-step goals autonomously, rather than producing a single response to a single prompt. Requires state management, tool orchestration, and often memory across steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;80. What is function calling / tool use, and how is it implemented under the hood?&lt;/strong&gt;&lt;br&gt;
The model is given structured tool definitions (name, description, parameter schema) in its context; it's fine-tuned to output a structured call (JSON) when a tool is appropriate instead of natural text, which the application layer parses, executes, and feeds the result back into context for the next turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;81. Explain the difference between multimodal and unimodal LLMs.&lt;/strong&gt;&lt;br&gt;
Unimodal models process a single modality (text). Multimodal models (GPT-4V, Gemini, LLaVA) process and often generate across modalities (text, images, audio) using techniques like vision encoders projected into the LLM's embedding space, enabling tasks like visual question answering and document understanding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;82. What is model merging and why has it become popular?&lt;/strong&gt;&lt;br&gt;
Model merging combines the weights of multiple fine-tuned models (via techniques like SLERP, TIES, or DARE) into a single model without additional training, often producing a model with combined capabilities of its parents. It's popular because it's compute-free relative to retraining and can improve robustness/generalization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;83. What is the difference between open-weight and open-source LLMs?&lt;/strong&gt;&lt;br&gt;
Open-weight models release the trained parameters (usable, fine-tunable) but not necessarily training data, code, or full methodology (e.g., Llama). Fully open-source models release weights, training code, and data recipes (e.g., OLMo, Pythia) enabling full reproducibility — an important distinction for licensing, auditability, and research.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;84. Explain the concept of test-time compute / inference-time scaling (as in o1-style reasoning models).&lt;/strong&gt;&lt;br&gt;
Instead of relying solely on model size for capability, these models spend additional compute at inference time generating extended internal reasoning chains before answering, trading latency/cost for improved accuracy on complex reasoning tasks — a new scaling axis alongside pre-training compute and data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;85. What is model collapse in the context of training on synthetic/AI-generated data?&lt;/strong&gt;&lt;br&gt;
When models are recursively trained on data generated by prior model generations without sufficient real-data anchoring, error and distributional narrowing compound over generations, degrading diversity and accuracy — a growing concern as synthetic data becomes a larger fraction of the web/training corpora.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;86. What is context caching / prompt caching offered by LLM providers, and how does it reduce cost?&lt;/strong&gt;&lt;br&gt;
Providers cache the KV state for a repeated prefix (e.g., a long system prompt or document) across requests, so subsequent calls sharing that prefix skip redundant prefill computation, significantly reducing latency and cost for applications with large, stable shared context (e.g., RAG systems with long document context).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;87. What is the role of synthetic data generation in modern LLM training pipelines?&lt;/strong&gt;&lt;br&gt;
Synthetic data (model-generated instructions, reasoning traces, or distillation data from stronger models) supplements scarce or expensive human-labeled data, especially for instruction-tuning and reasoning capability. It requires careful filtering/verification to avoid quality degradation or model collapse over successive generations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;88. How do sliding window attention and other long-context techniques work?&lt;/strong&gt;&lt;br&gt;
Sliding window attention restricts each token to attend only to a fixed-size local window (plus optionally a few global tokens), reducing compute from O(n²) to O(n·w). Combined with techniques like RoPE scaling, ALiBi, or hierarchical/hybrid attention, this enables extending effective context length beyond training-time limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;89. What is the difference between fine-tuning and RAG for injecting domain knowledge, and how do you choose?&lt;/strong&gt;&lt;br&gt;
Fine-tuning bakes knowledge/behavior into weights — good for style, format, and stable domain patterns, but expensive to update and prone to hallucination on facts. RAG keeps knowledge external and retrievable — better for frequently-changing or large factual corpora, with built-in citability. Most production systems combine both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;90. What are small language models (SLMs) and when are they the right architectural choice?&lt;/strong&gt;&lt;br&gt;
SLMs (typically &amp;lt;10B parameters) trade broad generality for efficiency, lower cost, and the ability to run on-device or at very high throughput. They're the right choice for narrow, well-defined tasks with sufficient fine-tuning data, latency-sensitive applications, or privacy-constrained on-device deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  H. Production, MLOps &amp;amp; System Design (Q91–100)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;91. How would you design an LLM-powered customer support system end-to-end?&lt;/strong&gt;&lt;br&gt;
Intent classification/routing → RAG over knowledge base for grounded answers → structured escalation logic for out-of-scope or low-confidence cases → human-in-the-loop for high-stakes actions → logging/feedback loop for continuous eval and fine-tuning → guardrails for PII and off-topic/harmful queries → monitoring dashboards for quality drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;92. What monitoring and observability practices are essential for production LLM systems?&lt;/strong&gt;&lt;br&gt;
Track latency (TTFT, total), cost per request, token usage, error/refusal rates, output quality via sampled human/LLM-judge review, drift in input distribution, hallucination/groundedness scores for RAG, and user feedback signals (thumbs up/down, escalation rate) — all with alerting on threshold breaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;93. How do you version and manage prompts in a production system?&lt;/strong&gt;&lt;br&gt;
Treat prompts as code: store in version control, use templating with parameterization, run regression evals on every change before deployment, support environment-specific configs (dev/staging/prod), and maintain a changelog correlating prompt versions with observed metric changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;94. What is prompt drift and how do you detect/prevent it?&lt;/strong&gt;&lt;br&gt;
Prompt drift occurs when underlying model updates (even "same" model versions from a provider) change behavior for an existing prompt, degrading production quality silently. Detect via continuous regression testing against a golden eval set on model/version changes; prevent by pinning model versions where possible and monitoring output metrics over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;95. How would you architect a multi-tenant LLM platform serving multiple internal teams?&lt;/strong&gt;&lt;br&gt;
Centralize model access via a gateway (auth, rate limiting, cost attribution per team), provide shared observability/logging infrastructure, support per-tenant configuration (models, prompts, guardrails), implement usage quotas and chargeback, and offer a self-service eval/testing framework so teams can safely iterate independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;96. What are the key cost drivers in an LLM application and how do you optimize them?&lt;/strong&gt;&lt;br&gt;
Input/output token volume, model tier choice, redundant calls (lack of caching), and retrieval overhead in RAG. Optimize via prompt compression, semantic caching, routing simple queries to cheaper models, batching where latency allows, and right-sizing context (avoid over-stuffing retrieved documents).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;97. How do you handle model deprecation and migration in a production system relying on a third-party LLM API?&lt;/strong&gt;&lt;br&gt;
Maintain an abstraction layer decoupling application logic from a specific provider/model, run the golden eval suite against candidate replacement models before cutover, do gradual/canary rollout with metric comparison, and keep prompts modular enough to require minimal rework across model families.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;98. What is the CI/CD equivalent for LLM applications ("LLMOps")?&lt;/strong&gt;&lt;br&gt;
Pipeline stages: prompt/data versioning → automated eval suite (regression + safety) on every change → staged rollout (canary/shadow traffic) → production monitoring with automated rollback triggers → periodic re-evaluation as underlying models/data evolve — analogous to traditional CI/CD but with non-deterministic, quality-based gating instead of pass/fail unit tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;99. How do you decide between building on a proprietary API (OpenAI/Anthropic) vs self-hosting an open-weight model?&lt;/strong&gt;&lt;br&gt;
Consider: data privacy/compliance requirements, latency/throughput needs, total cost at your volume (API per-token cost vs GPU infra + ops overhead), need for fine-tuning/customization, and required capability ceiling — proprietary APIs generally win for fastest time-to-market and top capability, self-hosting wins for cost-at-scale, data control, and customization depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;100. Describe a real (or realistic) production incident involving an LLM system and how you'd debug it.&lt;/strong&gt;&lt;br&gt;
Example: a RAG chatbot suddenly starts hallucinating incorrect answers. Debug path: check if it's isolated to specific query types (retrieval failure) vs global (model/prompt regression) → inspect retrieved context for the failing cases (are relevant docs even being retrieved?) → check for recent prompt/index/model version changes → replay failing cases against golden eval set → roll back the suspected change while root-causing, then add the failure pattern to the regression eval set to prevent recurrence.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2 — Retrieval-Augmented Generation (100 Q&amp;amp;A)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. RAG Fundamentals (Q1–15)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. What is RAG and what problem does it solve?&lt;/strong&gt;&lt;br&gt;
RAG combines a retrieval system with an LLM: relevant documents are fetched from an external knowledge source at query time and injected into the prompt as context, grounding generation in up-to-date, verifiable information. It addresses LLM knowledge cutoffs, hallucination, and the impracticality of retraining models for every knowledge update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Walk through the basic RAG pipeline architecture.&lt;/strong&gt;&lt;br&gt;
Ingestion: documents are chunked, embedded, and stored in a vector index. Query time: the user query is embedded, top-k similar chunks are retrieved, optionally reranked, assembled into a prompt with the query, and passed to the LLM to generate a grounded response, often with citations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What are the main components of a production RAG system?&lt;/strong&gt;&lt;br&gt;
Document loaders/parsers, chunking strategy, embedding model, vector store/index, retrieval logic (dense/sparse/hybrid), reranker, prompt assembly/context management, the generator LLM, and an evaluation/observability layer — each is independently tunable and a common point of failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Why does RAG reduce hallucination but not eliminate it?&lt;/strong&gt;&lt;br&gt;
Grounding the model in retrieved context reduces reliance on parametric (memorized, potentially stale/wrong) knowledge, but the model can still misread, over-generalize beyond, or ignore the retrieved context, or retrieval itself can fail to surface the correct documents — so hallucination risk is reduced, not removed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. What is the difference between RAG and fine-tuning for knowledge injection?&lt;/strong&gt;&lt;br&gt;
RAG externalizes knowledge (retrievable, updatable, citable, no retraining needed) while fine-tuning internalizes it into weights (better for behavior/style/format, but static and hard to audit/update). Most production systems use RAG for facts and fine-tuning for tone, format, and task-specific behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What is "naive RAG" vs "advanced RAG" vs "modular RAG"?&lt;/strong&gt;&lt;br&gt;
Naive RAG is the basic embed-retrieve-generate pipeline. Advanced RAG adds pre-retrieval (query rewriting, routing) and post-retrieval (reranking, compression) optimizations around the same core flow. Modular RAG treats retrieval, routing, and generation as composable, potentially iterative modules — enabling patterns like multi-hop retrieval or agentic RAG.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. What types of documents/data sources is RAG best suited for?&lt;/strong&gt;&lt;br&gt;
Best for large, frequently-updated, or proprietary text corpora where citability matters: internal knowledge bases, documentation, legal/compliance documents, customer support histories, and research papers. Less suited (alone) for tasks requiring complex multi-step numerical reasoning or data better served by structured queries (SQL) over databases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. What is grounding, and how do you measure how well a RAG response is grounded?&lt;/strong&gt;&lt;br&gt;
Grounding means every factual claim in the generated response is supported by the retrieved context. Measured via automated faithfulness metrics (NLI-based entailment checking of claims against source chunks), citation-attribution verification, or LLM-as-judge scoring against the retrieved documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. When would you NOT use RAG?&lt;/strong&gt;&lt;br&gt;
When the task doesn't require external/current knowledge (pure creative writing, general reasoning on self-contained input), when ultra-low latency is critical and retrieval adds unacceptable overhead, when the knowledge base is small enough to fit entirely in context, or when structured data querying (SQL/API) is more appropriate than semantic retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. What is "context stuffing" and why is it a poor default strategy?&lt;/strong&gt;&lt;br&gt;
Context stuffing means passing as many retrieved documents as possible to maximize the chance of including relevant info. It's poor because it increases cost/latency, risks the "lost in the middle" problem diluting relevant content, and can actually reduce answer quality — well-tuned top-k with reranking usually beats brute-force stuffing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. Explain the trade-off between retrieval precision and recall in RAG.&lt;/strong&gt;&lt;br&gt;
High recall (retrieve broadly) ensures relevant information isn't missed but risks diluting context with noise, hurting generation quality and increasing cost. High precision (retrieve narrowly) keeps context focused but risks missing needed information. Production systems tune this via top-k, similarity thresholds, and reranking to balance both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. What is multi-hop retrieval and when is it needed?&lt;/strong&gt;&lt;br&gt;
Multi-hop retrieval performs sequential retrieval steps where each retrieved result informs the next query (e.g., answering "What company did the founder of X work at before?" requires first retrieving who founded X, then retrieving that person's work history). Needed for compositional questions that a single retrieval pass can't answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. What is agentic RAG?&lt;/strong&gt;&lt;br&gt;
Agentic RAG gives the LLM autonomy over the retrieval process itself — deciding whether to retrieve, reformulating queries, choosing which knowledge source to query, evaluating if retrieved results are sufficient, and iterating (retrieve-generate-critique loops) rather than following a fixed single-pass pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. What is the difference between RAG and long-context LLMs — does long context make RAG obsolete?&lt;/strong&gt;&lt;br&gt;
Long-context models can ingest entire documents directly, but at higher cost/latency and with degraded recall on very long inputs ("lost in the middle"). RAG remains valuable for cost efficiency, citability, freshness (no need to re-embed the whole corpus into every prompt), and scaling to corpora far larger than any context window. They're often complementary, not competing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. Explain corrective RAG (CRAG) and self-RAG.&lt;/strong&gt;&lt;br&gt;
CRAG adds a lightweight evaluator that grades retrieved documents' relevance; if retrieval quality is poor, it triggers corrective actions like web search or query rewriting before generation. Self-RAG trains the LLM itself to emit special reflection tokens deciding when to retrieve, critiquing retrieved passages, and assessing its own output's support — both aim to make RAG more robust to poor retrieval.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Chunking &amp;amp; Preprocessing (Q16–30)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;16. Why does chunking strategy significantly impact RAG quality?&lt;/strong&gt;&lt;br&gt;
Chunk size and boundaries determine what semantic units are retrievable and embeddable — too large dilutes relevance signal and wastes context; too small loses necessary context/coherence for the LLM to answer correctly. Poor chunking is one of the most common root causes of RAG failures in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. Compare fixed-size, recursive, semantic, and document-structure-aware chunking.&lt;/strong&gt;&lt;br&gt;
Fixed-size (token/character count) is simple but ignores semantic boundaries. Recursive chunking splits along a hierarchy of separators (paragraphs → sentences) to respect structure while hitting size targets. Semantic chunking groups sentences by embedding similarity to keep coherent ideas together. Structure-aware chunking respects document elements (headings, tables, code blocks) — generally the best-performing but most implementation-heavy approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What is chunk overlap and why is it used?&lt;/strong&gt;&lt;br&gt;
Overlap (repeating a portion of text between adjacent chunks) prevents important context from being split awkwardly across a chunk boundary, ensuring a query can still retrieve the full relevant passage even if the key sentence straddles two chunks. Typical overlap is 10-20% of chunk size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. How do you handle tables, code, and structured content during chunking?&lt;/strong&gt;&lt;br&gt;
Extract and preserve structural integrity — keep tables intact (or convert to a serialized text/markdown representation) rather than splitting rows arbitrarily; treat code blocks as atomic units; use specialized parsers (e.g., unstructured.io, layout-aware PDF parsers) rather than naive text extraction that would mangle these elements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. What is parent-child (small-to-big) chunking retrieval?&lt;/strong&gt;&lt;br&gt;
Small, precise chunks are embedded and used for retrieval matching (better semantic precision), but when a small chunk is retrieved, its larger parent chunk/section is what's actually passed to the LLM for generation — combining precise retrieval with sufficient context for coherent answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21. How do you determine the optimal chunk size for a given use case?&lt;/strong&gt;&lt;br&gt;
It depends on the embedding model's effective context window, the nature of the content (dense technical text vs conversational), and the granularity of expected queries (fact lookup favors smaller chunks, summarization/broad questions favor larger). Empirically tune via retrieval eval metrics (recall@k) rather than picking a size a priori.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22. What is metadata filtering in RAG and why is it important?&lt;/strong&gt;&lt;br&gt;
Attaching structured metadata (date, author, document type, department, access level) to chunks allows retrieval to be filtered/scoped before or alongside semantic search — critical for multi-tenant access control, recency requirements, and narrowing large heterogeneous corpora to relevant subsets, improving both precision and compliance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23. How do you handle document updates and deletions in a RAG index without stale data?&lt;/strong&gt;&lt;br&gt;
Maintain a mapping from source document to its chunk IDs so updates can delete-and-reindex just the affected chunks; use versioning/timestamps to prefer freshest content; implement periodic full re-sync jobs alongside incremental updates; and avoid orphaned chunks by ensuring deletions propagate to the vector store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24. What is contextual retrieval (Anthropic's technique) and why does it improve results?&lt;/strong&gt;&lt;br&gt;
Contextual retrieval prepends a short, LLM-generated summary of how a chunk relates to the overall document before embedding/indexing it, so the chunk's embedding and BM25 representation carry document-level context it would otherwise lose in isolation — shown to significantly reduce retrieval failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25. How should you handle multi-modal documents (PDFs with images, charts, scanned pages) in a RAG pipeline?&lt;/strong&gt;&lt;br&gt;
Use layout-aware extraction (e.g., OCR for scanned content, vision-language models to caption charts/images), preserve reading order and structural hierarchy, consider multi-modal embeddings for image content, and store extracted descriptions alongside/instead of raw images depending on whether visual retrieval is needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26. What preprocessing steps matter before chunking (cleaning, deduplication, normalization)?&lt;/strong&gt;&lt;br&gt;
Remove boilerplate (headers/footers/navigation), deduplicate near-identical content across sources, normalize whitespace/encoding, resolve or strip broken formatting artifacts from extraction, and standardize date/number formats — noisy input directly degrades embedding quality and retrieval precision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;27. How do you chunk long-form content like books or lengthy legal contracts differently from short documents like FAQs?&lt;/strong&gt;&lt;br&gt;
Long-form content benefits from hierarchical chunking (section → paragraph) with parent-child retrieval to preserve context, and larger overlap given denser cross-references. Short documents (FAQs, short articles) often work best as whole-document or single-chunk units, since splitting can destroy the atomic Q&amp;amp;A structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;28. What is sliding window chunking and its trade-offs?&lt;/strong&gt;&lt;br&gt;
A fixed-size window moves through the document with a defined stride shorter than the window size, creating overlapping chunks. It maximizes context preservation across boundaries but multiplies storage/embedding cost and can introduce near-duplicate chunks that skew retrieval ranking if not deduplicated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;29. How do you evaluate whether your chunking strategy is effective?&lt;/strong&gt;&lt;br&gt;
Measure retrieval recall@k on a labeled eval set (does the correct chunk get retrieved for known queries), inspect chunk boundaries manually for semantic coherence on samples, and run end-to-end answer quality evaluation comparing different chunking configurations — chunking should be evaluated empirically, not assumed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30. What is late chunking and how does it differ from traditional chunking?&lt;/strong&gt;&lt;br&gt;
Late chunking runs the full document through a long-context embedding model first to get token-level contextualized embeddings, then pools/splits into chunks afterward — so each chunk's embedding still carries full-document context, addressing the context-loss problem of chunking before embedding.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Embeddings &amp;amp; Vector Databases (Q31–45)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;31. What is a text embedding and how is it generated?&lt;/strong&gt;&lt;br&gt;
An embedding is a dense vector representation of text where semantic similarity corresponds to geometric proximity (e.g., cosine similarity). Generated by encoder models (BERT-derivatives, or dedicated embedding models like OpenAI's text-embedding-3 or open models like BGE/E5) trained via contrastive learning on similar/dissimilar text pairs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32. How do you choose an embedding model for a RAG system?&lt;/strong&gt;&lt;br&gt;
Consider retrieval benchmark performance for your domain (MTEB leaderboard as a starting reference), embedding dimensionality (trade-off between quality and storage/speed), max input token length, multilingual support if needed, licensing/cost (API vs self-hosted), and empirical evaluation on your own labeled query-document pairs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;33. What is the difference between dense and sparse retrieval?&lt;/strong&gt;&lt;br&gt;
Dense retrieval uses learned embeddings and semantic (cosine/dot-product) similarity, capturing meaning beyond exact word match. Sparse retrieval (TF-IDF, BM25) uses term-frequency-based statistics over exact tokens, excelling at keyword/exact-match queries (IDs, rare terms, jargon) where dense models can underperform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;34. What is hybrid search and why does it typically outperform pure dense or sparse retrieval?&lt;/strong&gt;&lt;br&gt;
Hybrid search combines dense (semantic) and sparse (lexical/BM25) retrieval results, typically fused via reciprocal rank fusion or weighted scoring, capturing both semantic similarity and exact keyword matches. It's more robust because dense and sparse methods fail on different query types, so combining covers each other's blind spots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;35. Explain how vector similarity search works (cosine similarity, dot product, Euclidean distance).&lt;/strong&gt;&lt;br&gt;
Cosine similarity measures the angle between vectors (magnitude-invariant, most common for normalized embeddings). Dot product incorporates magnitude and is used when embedding norms carry meaningful signal (often equivalent to cosine after normalization). Euclidean (L2) distance measures straight-line distance — choice should match how the embedding model was trained/optimized.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;36. What is Approximate Nearest Neighbor (ANN) search and why is it necessary at scale?&lt;/strong&gt;&lt;br&gt;
Exact nearest-neighbor search is O(n) per query — infeasible at millions/billions of vectors. ANN algorithms (HNSW, IVF, LSH) trade a small amount of recall accuracy for orders-of-magnitude speedup by building index structures that avoid exhaustive comparison, making large-scale vector search practical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;37. Explain HNSW (Hierarchical Navigable Small World) at a conceptual level.&lt;/strong&gt;&lt;br&gt;
HNSW builds a multi-layer graph where higher layers have fewer, longer-range connections (for fast coarse navigation) and lower layers have denser, short-range connections (for fine-grained search). Search starts at the top layer and greedily descends, giving logarithmic-ish search complexity with high recall — the most widely used ANN algorithm in production vector databases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;38. How do you choose between vector database options (Pinecone, Weaviate, Milvus, pgvector, Qdrant, FAISS)?&lt;/strong&gt;&lt;br&gt;
Consider: managed vs self-hosted trade-off, scale requirements (billions of vectors need distributed architectures), metadata filtering capabilities, hybrid search support, existing infra (pgvector fits naturally if already on Postgres), latency SLAs, and cost — FAISS is a library (not a full DB) best for embedded/research use cases, not multi-tenant production serving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;39. What is vector index quantization (PQ, scalar quantization) and why use it?&lt;/strong&gt;&lt;br&gt;
Product Quantization (PQ) and scalar quantization compress vector representations to reduce memory footprint and speed up distance computation, at the cost of some precision loss. Essential when indexing billions of vectors where full-precision storage would be prohibitively expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;40. How do you handle embedding model versioning/upgrades without breaking a live RAG index?&lt;/strong&gt;&lt;br&gt;
Since different embedding model versions produce incompatible vector spaces, you generally must fully re-embed and re-index the entire corpus when upgrading models — plan for a shadow index built with the new model, validate retrieval quality against the old one, then cut over atomically rather than mixing embeddings from different models in one index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;41. What is re-embedding drift and how do you monitor for it?&lt;/strong&gt;&lt;br&gt;
Drift occurs when the distribution of incoming queries/documents shifts over time relative to what the embedding model was optimized for, degrading retrieval quality silently. Monitor via periodic retrieval eval on a fixed labeled set, tracking recall@k over time, and analyzing query logs for emerging out-of-distribution patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;42. Explain Matryoshka embeddings and why they're useful.&lt;/strong&gt;&lt;br&gt;
Matryoshka Representation Learning trains embeddings so that truncating the vector to a smaller dimension (e.g., 768 → 128) still yields a usable, if less precise, embedding. This lets a single model serve multiple storage/speed tiers by truncating dimensions as needed, without training separate models per dimensionality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;43. What is the difference between bi-encoders and cross-encoders in the retrieval context?&lt;/strong&gt;&lt;br&gt;
Bi-encoders embed query and document independently, enabling fast pre-computed vector search at scale but losing fine-grained query-document interaction. Cross-encoders jointly process the query-document pair through a single model for much higher accuracy, but are too slow to run over an entire corpus — hence used for reranking a small candidate set, not initial retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;44. How do you handle multi-lingual retrieval in a RAG system?&lt;/strong&gt;&lt;br&gt;
Use multilingual embedding models trained on cross-lingual contrastive data (e.g., multilingual-E5, LaBSE) so semantically equivalent text in different languages maps close together in vector space, enabling cross-lingual retrieval (query in one language, documents in another) without translation as an intermediate step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;45. What causes "semantic drift" between a query and retrieved chunks, and how do you mitigate it?&lt;/strong&gt;&lt;br&gt;
Short, ambiguous, or jargon-heavy queries may embed far from relevant document phrasing even when topically related, due to vocabulary/style mismatch. Mitigate with query expansion/rewriting, contextual retrieval (enriching chunks with context before embedding), hybrid search to catch exact-term matches dense search misses, and fine-tuning the embedding model on domain-specific query-document pairs.&lt;/p&gt;

&lt;h3&gt;
  
  
  D. Retrieval Strategies (Q46–60)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;46. What is query rewriting/expansion and why is it used in RAG?&lt;/strong&gt;&lt;br&gt;
The original user query is transformed (via LLM rephrasing, synonym expansion, or decomposition into sub-questions) before retrieval to better match how relevant information is phrased in the corpus, improving recall especially for short, ambiguous, or conversational queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;47. Explain HyDE (Hypothetical Document Embeddings).&lt;/strong&gt;&lt;br&gt;
Instead of embedding the raw query, an LLM first generates a hypothetical answer/document that would satisfy the query, and that hypothetical document's embedding is used for retrieval — since document-like text often embeds closer to actual relevant documents than a short question does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;48. What is query decomposition and when is it necessary?&lt;/strong&gt;&lt;br&gt;
Complex, multi-part questions are broken into simpler sub-questions, each retrieved and answered independently (or sequentially, feeding into each other), then synthesized into a final answer. Necessary for compositional/multi-hop questions that a single retrieval pass over the original query wouldn't resolve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;49. What is routing in RAG, and how does it work in multi-source systems?&lt;/strong&gt;&lt;br&gt;
A routing layer (often a lightweight LLM classifier or embedding-based classifier) decides which knowledge source, index, or retrieval strategy to use for a given query — e.g., routing a "pricing" query to a product database and a "how-to" query to documentation, improving relevance and efficiency in systems spanning multiple heterogeneous sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;50. Explain Reciprocal Rank Fusion (RRF) for combining multiple retrieval result lists.&lt;/strong&gt;&lt;br&gt;
RRF combines rankings from multiple retrieval methods (e.g., dense + sparse) by scoring each document as the sum of 1/(k + rank) across all lists it appears in, rewarding documents that rank well across multiple methods without needing to normalize disparate similarity score scales — simple and robust for hybrid search fusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;51. What is self-querying retrieval?&lt;/strong&gt;&lt;br&gt;
An LLM parses the natural language query to automatically extract structured filter conditions (e.g., "papers from 2023 about transformers" → semantic query "transformers" + metadata filter year=2023), combining semantic search with precise structured filtering without the user needing to specify filters explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;52. How do you implement retrieval over structured data (SQL databases) combined with unstructured RAG?&lt;/strong&gt;&lt;br&gt;
Use a routing/agentic layer where the LLM determines if a query needs structured data (generates and executes SQL against the database, often called "Text-to-SQL") versus unstructured retrieval (vector search), or combines both — sometimes called hybrid or "structured RAG."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;53. What is GraphRAG and when would you use a knowledge graph over standard vector retrieval?&lt;/strong&gt;&lt;br&gt;
GraphRAG builds/uses a knowledge graph of entities and relationships extracted from the corpus, enabling retrieval that follows explicit relational structure (multi-hop entity relationships) rather than just semantic similarity — valuable for questions requiring relational reasoning (e.g., "who are all the people connected to X through Y") that vector similarity alone struggles with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;54. What is iterative/recursive retrieval?&lt;/strong&gt;&lt;br&gt;
The system retrieves, generates an intermediate answer or assessment, and uses that to inform a subsequent retrieval query, repeating until sufficient information is gathered or a stopping criterion is met — useful for complex research-style questions requiring progressively refined information gathering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;55. How do you handle retrieval for conversational (multi-turn) RAG where queries depend on prior context?&lt;/strong&gt;&lt;br&gt;
Rewrite/contextualize the current turn's query using conversation history (often via an LLM call that resolves references like "it" or "that" into an explicit standalone query) before running retrieval, since raw follow-up queries in isolation often lack the context needed for accurate retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;56. What is the "needle in a haystack" test and how does it relate to retrieval evaluation?&lt;/strong&gt;&lt;br&gt;
It tests whether a system can retrieve/recall a specific fact ("needle") planted within a large volume of distractor content ("haystack") at varying positions and context lengths — commonly used to evaluate both long-context LLMs' recall and RAG retrieval pipelines' ability to surface sparse relevant information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;57. What is negative/hard-negative mining and why does it matter for retrieval quality?&lt;/strong&gt;&lt;br&gt;
Hard negatives are documents that are superficially similar (high embedding similarity) but actually irrelevant/incorrect. Including them during embedding model fine-tuning (contrastive training) sharpens the model's ability to discriminate fine-grained relevance, significantly improving retrieval precision over training with only random negatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;58. How do you handle retrieval when the answer isn't explicitly present in any single document (requires synthesis across multiple)?&lt;/strong&gt;&lt;br&gt;
Retrieve a broader set of top-k relevant chunks across potentially multiple documents, ensure the prompt explicitly instructs the LLM to synthesize across sources, and consider multi-hop or iterative retrieval to progressively gather the necessary pieces before final generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;59. What is time-aware or recency-biased retrieval, and how do you implement it?&lt;/strong&gt;&lt;br&gt;
For domains where newer information should be preferred (news, pricing, policy), combine semantic similarity score with a recency decay factor (e.g., exponential decay by document age) in the final ranking, or apply hard metadata filters/boosts for date ranges, rather than relying on semantic similarity alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;60. How would you design retrieval for a RAG system with strict document-level access control (multi-tenant enterprise)?&lt;/strong&gt;&lt;br&gt;
Enforce access control at the retrieval layer, not just the UI — filter the vector search itself by tenant/permission metadata (never retrieve, even into the LLM context, documents the user isn't authorized to see), and audit-log retrieval access for compliance; never rely on the LLM to "choose not to use" unauthorized content it was given.&lt;/p&gt;

&lt;h3&gt;
  
  
  E. Reranking &amp;amp; Fusion (Q61–70)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;61. What is reranking and why is it added as a separate stage after initial retrieval?&lt;/strong&gt;&lt;br&gt;
Initial retrieval (dense/hybrid, often over the full corpus) prioritizes speed via cheaper bi-encoder similarity. Reranking applies a more expensive but more accurate cross-encoder (or LLM-based) model to just the small candidate set (e.g., top 50-100), re-ordering by finer-grained relevance before passing the final top-k to the LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;62. Compare cross-encoder rerankers vs LLM-based reranking.&lt;/strong&gt;&lt;br&gt;
Cross-encoder rerankers (e.g., Cohere Rerank, BGE-reranker) are purpose-trained, fast, and cost-effective for scoring query-document relevance. LLM-based reranking (prompting a general LLM to score/rank candidates) can incorporate more nuanced/contextual judgment and reasoning but is slower and more expensive — choice depends on latency/cost budget vs required nuance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;63. What is Maximal Marginal Relevance (MMR) and what problem does it solve?&lt;/strong&gt;&lt;br&gt;
MMR re-ranks retrieved results to balance relevance with diversity, penalizing candidates too similar to already-selected results — preventing the top-k from being near-duplicate chunks (e.g., five near-identical paragraphs from the same section) and ensuring broader coverage of distinct relevant information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;64. How does reranking improve overall RAG answer quality beyond just retrieval metrics?&lt;/strong&gt;&lt;br&gt;
By ensuring the most genuinely relevant chunks occupy the positions in context the LLM attends to most (start/end, per "lost in the middle"), and by filtering out superficially-similar-but-irrelevant chunks that would otherwise dilute or confuse generation — directly improving downstream answer accuracy, not just retrieval recall metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;65. What is context compression in RAG pipelines?&lt;/strong&gt;&lt;br&gt;
After retrieval (and optionally reranking), an additional step extracts/summarizes only the most relevant sentences/spans from each retrieved chunk (rather than passing full chunks), reducing token usage and noise while preserving the information needed to answer the query — useful when retrieved chunks are large but only partially relevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;66. How do you decide the optimal top-k value to retrieve before and after reranking?&lt;/strong&gt;&lt;br&gt;
Tune empirically: retrieve a broader initial candidate set (e.g., top-50) to maximize recall cheaply, then rerank down to a smaller final set (e.g., top-5) balancing context window budget, cost, and the "lost in the middle" risk of over-including — validate via end-to-end answer quality eval, not retrieval metrics alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;67. What is Cohere Rerank / BGE-reranker and how are they typically integrated into a pipeline?&lt;/strong&gt;&lt;br&gt;
These are purpose-built cross-encoder models exposed via API or open weights that take a (query, document) pair and output a relevance score. Integrated as a post-retrieval step: pass the top-N candidates from initial vector search through the reranker, sort by its score, and take the final top-k for the LLM prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;68. What are the latency/cost trade-offs of adding a reranking stage, and how do you justify it?&lt;/strong&gt;&lt;br&gt;
Reranking adds an extra model call (latency + cost) per query, but the resulting improvement in context relevance often meaningfully increases answer accuracy and reduces hallucination — justified when initial retrieval precision is a bottleneck, which you'd confirm via ablation testing (measuring answer quality with/without reranking on your eval set).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;69. What is score fusion and how does it differ from rank fusion (like RRF)?&lt;/strong&gt;&lt;br&gt;
Score fusion combines raw similarity/relevance scores (often after normalization, e.g., min-max scaling) from multiple retrieval methods via weighted sum. Rank fusion (RRF) instead combines methods based on each document's rank position, sidestepping the challenge of normalizing incompatible score scales across different retrieval algorithms — RRF is generally more robust when combining heterogeneous methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;70. How would you handle a case where reranking consistently demotes a document type that's actually important (e.g., short FAQ answers get outranked by longer docs)?&lt;/strong&gt;&lt;br&gt;
Investigate reranker bias (many cross-encoders have length bias favoring longer text), consider chunk-type-aware boosting/normalization, fine-tune or select a reranker evaluated specifically on your document type distribution, or apply post-reranking business rules ensuring minimum representation from key document categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  F. Generation &amp;amp; Prompting for RAG (Q71–80)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;71. How should a RAG system prompt be structured to maximize grounded, accurate answers?&lt;/strong&gt;&lt;br&gt;
Clearly separate instructions, retrieved context (with source labels), and the user query; explicitly instruct the model to answer only from the provided context and to say "I don't know" if the context is insufficient; request citations pointing to specific sources; and keep instructions concise to avoid diluting attention on the actual context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;72. How do you instruct an LLM to say "I don't know" instead of hallucinating when retrieval fails?&lt;/strong&gt;&lt;br&gt;
Explicitly prompt the model that it's acceptable and expected to state uncertainty or lack of information rather than guess, provide few-shot examples of "insufficient context" cases, and pair this with a retrieval-confidence check (e.g., low similarity scores) that can short-circuit generation entirely before the LLM is even prompted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;73. How do you implement citation generation in RAG responses?&lt;/strong&gt;&lt;br&gt;
Label each retrieved chunk with a source identifier in the prompt, instruct the model to reference sources by ID inline in its answer (e.g., "[1]"), and post-process to map those IDs back to actual document links/titles for the UI — some approaches instead do post-hoc attribution by matching generated sentences back to source chunks via NLI/similarity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;74. What is the risk of over-reliance on retrieved context vs the model's parametric knowledge, and how do you balance it?&lt;/strong&gt;&lt;br&gt;
Over-reliance purely on retrieved context can produce brittle, overly narrow answers if retrieval is imperfect; over-reliance on parametric knowledge risks outdated/hallucinated facts. Balance via prompting that prioritizes retrieved context for facts while allowing general reasoning/synthesis, and evaluation that specifically checks for contradictions between the two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;75. How do you handle conflicting information across multiple retrieved documents in generation?&lt;/strong&gt;&lt;br&gt;
Instruct the model explicitly to surface and acknowledge conflicts rather than silently picking one source, prioritize by metadata signals (recency, authority/source trust score) when available, and consider a stricter mode where genuinely conflicting critical information triggers a clarification request rather than a confident single answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;76. What is the impact of retrieved context ordering on generation quality, and how do you optimize it?&lt;/strong&gt;&lt;br&gt;
Given the "lost in the middle" effect, placing the most relevant/highest-confidence retrieved chunks at the beginning and/or end of the context (rather than by arbitrary or purely rank order in the middle) can measurably improve the model's use of that information in its answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;77. How do you prevent the LLM from "leaking" instructions or internal system prompt content in RAG responses?&lt;/strong&gt;&lt;br&gt;
Clearly demarcate system instructions from context/user content with structural boundaries, explicitly instruct the model not to reveal system instructions, test with adversarial prompts probing for leakage, and apply output-side filtering as a backstop guardrail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;78. What is answer synthesis across multiple documents, and what generation strategies help produce coherent multi-source answers?&lt;/strong&gt;&lt;br&gt;
Rather than treating each chunk independently, prompt the model to identify overlapping/complementary information across sources and produce a unified, non-redundant answer; techniques like map-reduce summarization (summarize each source, then synthesize summaries) help when the number of relevant sources is large.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;79. How do you handle cases where the user query is a follow-up that references the previous RAG answer, not just the original documents?&lt;/strong&gt;&lt;br&gt;
Include recent conversation turns (including the assistant's prior grounded answer) in the generation context alongside newly retrieved documents, and consider whether new retrieval is even needed for a given follow-up (some follow-ups are pure clarification/reformatting of already-retrieved information).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;80. What is the role of temperature/sampling settings specifically in RAG generation, and how should they differ from open-ended generation?&lt;/strong&gt;&lt;br&gt;
RAG generation typically benefits from lower temperature (more deterministic, closer to greedy decoding) since the goal is faithful grounding to retrieved facts rather than creative diversity — high temperature increases the risk of the model deviating from or embellishing beyond the provided context.&lt;/p&gt;

&lt;h3&gt;
  
  
  G. Evaluation of RAG Systems (Q81–90)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;81. What are the key dimensions to evaluate in a RAG system?&lt;/strong&gt;&lt;br&gt;
Retrieval quality (are the right documents found — precision/recall), groundedness/faithfulness (does the answer stick to retrieved content), answer relevance (does it actually address the query), and end-to-end correctness — each requires distinct metrics since a failure in any one stage can look like a generation problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;82. Explain the RAGAS evaluation framework and its core metrics.&lt;/strong&gt;&lt;br&gt;
RAGAS provides LLM-based reference-free metrics: faithfulness (are claims in the answer supported by retrieved context), answer relevance (does the answer address the query), context precision (are retrieved chunks actually relevant, ranked appropriately), and context recall (was all necessary information retrieved) — enabling automated RAG evaluation without needing large hand-labeled ground truth sets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;83. How do you evaluate retrieval quality independent of generation quality?&lt;/strong&gt;&lt;br&gt;
Use a labeled eval set of (query, relevant document/chunk) pairs and measure standard IR metrics: Recall@k (is the relevant doc in the top-k), Precision@k, Mean Reciprocal Rank (MRR), and NDCG (accounting for ranking order) — isolating retrieval evaluation helps pinpoint whether failures originate in retrieval or generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;84. What is context precision and context recall in RAG evaluation, and why measure both?&lt;/strong&gt;&lt;br&gt;
Context recall measures whether all necessary relevant information was retrieved (missing it caps the best possible answer quality). Context precision measures how much of the retrieved context was actually relevant/useful (low precision wastes context budget and can dilute/confuse generation) — a system can be strong on one and weak on the other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;85. How do you build a labeled evaluation dataset for RAG when you don't have pre-existing ground truth?&lt;/strong&gt;&lt;br&gt;
Sample representative real (or synthetic, LLM-generated) queries, have domain experts (or a strong LLM with human verification) identify the ground-truth relevant document(s) and ideal answer for each, and periodically expand the set with production failure cases discovered through monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;86. What is answer faithfulness/groundedness and how is it typically measured automatically?&lt;/strong&gt;&lt;br&gt;
Faithfulness measures whether every factual claim in the generated answer is entailed by the retrieved context (not the model's external/parametric knowledge). Measured by decomposing the answer into atomic claims and checking each against the context via NLI models or an LLM-judge prompted specifically for entailment checking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;87. How do you evaluate a RAG system's handling of "unanswerable" queries (where no relevant context exists)?&lt;/strong&gt;&lt;br&gt;
Include deliberately unanswerable queries in your eval set and measure whether the system correctly abstains/states uncertainty rather than hallucinating a confident-sounding but ungrounded answer — this is a critical, often-overlooked failure mode distinct from standard answer-quality metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;88. What is the role of human evaluation in RAG systems, and when is it indispensable over automated metrics?&lt;/strong&gt;&lt;br&gt;
Human eval remains indispensable for judging nuanced answer quality (tone, completeness, subtle factual errors automated NLI checks miss), calibrating/validating automated LLM-judge metrics periodically, and evaluating on genuinely novel or ambiguous production queries not covered by static eval sets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;89. How do you set up continuous evaluation/regression testing for a RAG system in production?&lt;/strong&gt;&lt;br&gt;
Maintain a golden eval set covering key query types and known edge cases, run it automatically on every pipeline change (chunking strategy, embedding model, prompt, reranker), track metric trends over time (not just pass/fail thresholds), and periodically refresh the eval set with real production failure cases surfaced by monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;90. How do you diagnose whether a RAG failure is caused by retrieval or generation?&lt;/strong&gt;&lt;br&gt;
Manually inspect the actual retrieved context for the failing query: if the correct information wasn't retrieved at all, it's a retrieval failure (fix chunking/embedding/query rewriting); if the correct information was present in context but the answer is still wrong, it's a generation/faithfulness failure (fix prompting or consider a different generator model).&lt;/p&gt;

&lt;h3&gt;
  
  
  H. Production RAG &amp;amp; Scaling (Q91–100)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;91. How do you scale a RAG system to handle millions of documents and high query throughput?&lt;/strong&gt;&lt;br&gt;
Use a distributed vector database with sharding/replication, ANN indexing (HNSW/IVF) tuned for the recall/latency trade-off at scale, caching for frequent queries, asynchronous/batched ingestion pipelines, and horizontal scaling of the retrieval and generation service layers independently since they have different resource profiles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;92. What is the ingestion pipeline architecture for keeping a RAG index continuously up to date?&lt;/strong&gt;&lt;br&gt;
An event-driven or scheduled pipeline that detects source document changes (webhooks, polling, or CDC from a source system), triggers re-chunking/re-embedding only for changed documents, and atomically updates the vector index — designed for incremental updates rather than full reprocessing to keep latency and cost manageable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;93. How do you handle RAG system latency budgets when multiple stages (query rewrite, retrieval, rerank, generation) each add time?&lt;/strong&gt;&lt;br&gt;
Profile and budget latency per stage against your SLA, parallelize independent steps where possible (e.g., hybrid dense+sparse retrieval concurrently), use faster/smaller models for lower-value stages (query rewriting can use a small model), cache aggressively, and consider streaming the final generation to improve perceived latency even if total time is unchanged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;94. What is the cost breakdown of a typical production RAG system, and where do costs typically concentrate?&lt;/strong&gt;&lt;br&gt;
Costs concentrate in: embedding generation (especially at ingestion of large corpora), vector database hosting/compute, reranking API calls, and LLM generation tokens (both input context and output) — generation token cost from large context windows is often the single largest recurring cost driver at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;95. How would you architect a RAG system to support real-time/streaming data sources (e.g., live chat logs, news feeds)?&lt;/strong&gt;&lt;br&gt;
Use a streaming ingestion pipeline (e.g., Kafka-fed) that chunks/embeds/indexes documents near-real-time, apply short TTLs or recency boosting in retrieval ranking, and separate "hot" (recent, fast-changing) from "cold" (stable, archival) indices if freshness and query patterns differ significantly between them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;96. What security/compliance considerations are unique to enterprise RAG systems?&lt;/strong&gt;&lt;br&gt;
Document-level access control enforced at the retrieval layer (not just UI), PII detection/redaction in ingested content, audit logging of what content was retrieved/shown to which user, data residency compliance for vector storage, and ensuring the LLM provider's data retention policy meets contractual/regulatory requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;97. How do you handle RAG for extremely large individual documents (e.g., 500-page technical manuals)?&lt;/strong&gt;&lt;br&gt;
Use hierarchical chunking/summarization (chapter → section → paragraph summaries feeding a navigable tree), parent-child retrieval to keep precise chunk-level matching while providing broader context on retrieval, and consider a table-of-contents/routing step to first narrow down the relevant section before fine-grained retrieval within it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;98. What are common failure modes you'd look for when debugging a production RAG system with declining quality?&lt;/strong&gt;&lt;br&gt;
Silent embedding/reranker model drift or version mismatch, stale index (ingestion pipeline broken), chunking regressions from a document parser update, prompt template changes, context window overflow silently truncating retrieved content, and shifts in the production query distribution away from what the system was tuned for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;99. How do you A/B test changes to a RAG pipeline (e.g., new chunking strategy or reranker) safely in production?&lt;/strong&gt;&lt;br&gt;
Run the new configuration on a shadow/canary traffic split, compare against the golden eval set metrics offline first, then compare real user engagement/satisfaction signals between control and treatment groups at small scale before full rollout, with automated rollback triggers if key quality metrics regress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;100. Design a RAG system for a domain with extremely high accuracy requirements (e.g., legal or medical) — what additional safeguards would you add?&lt;/strong&gt;&lt;br&gt;
Mandatory citation-to-source for every claim with UI links to original documents, stricter groundedness thresholds that trigger abstention over guessing, human-in-the-loop review for high-stakes outputs, retrieval from only vetted/authoritative sources with clear provenance, extensive domain-expert-curated eval sets, and conservative low-temperature generation with explicit confidence signaling in the response.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3 — Model Context Protocol / MCP (100 Q&amp;amp;A)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. MCP Fundamentals (Q1–15)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. What is the Model Context Protocol (MCP)?&lt;/strong&gt;&lt;br&gt;
MCP is an open standard (introduced by Anthropic) that defines a common protocol for connecting LLM applications ("hosts") to external tools, data sources, and systems ("servers"), analogous to how USB-C standardized device connectivity — instead of every application building custom, one-off integrations for every tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. What problem does MCP solve that existed before it?&lt;/strong&gt;&lt;br&gt;
Before MCP, every LLM application had to build bespoke integration code for each external tool/data source it wanted to use (an "M×N" integration problem: M applications × N tools = M×N custom integrations). MCP standardizes this into an "M+N" problem — each tool builds one MCP server, each application builds one MCP client, and they interoperate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What are the three core primitives MCP servers expose?&lt;/strong&gt;&lt;br&gt;
Tools (executable functions the model can invoke, like an API call), Resources (structured/unstructured data the application can read and include as context, like files or database records), and Prompts (reusable, parameterized prompt templates the server provides for common interactions).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Explain the MCP host-client-server architecture.&lt;/strong&gt;&lt;br&gt;
The Host is the LLM application (e.g., Claude Desktop, an IDE, a custom agent) that manages the overall interaction and user-facing experience. It runs one or more Clients, each maintaining a dedicated 1:1 connection to a Server, which exposes tools/resources/prompts. A single host can connect to many servers simultaneously via separate clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. What transport protocols does MCP support?&lt;/strong&gt;&lt;br&gt;
Primarily stdio (standard input/output, for local processes — simple, low-latency, no network exposure) and HTTP with Server-Sent Events / Streamable HTTP (for remote servers accessed over a network), allowing MCP servers to run either as local subprocesses or as remotely hosted services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What underlying protocol/format does MCP use for messages?&lt;/strong&gt;&lt;br&gt;
MCP uses JSON-RPC 2.0 as its message format, defining structured requests, responses, and notifications between client and server, giving it a well-established, language-agnostic wire format with clear semantics for request/response correlation and error handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. How is MCP different from a traditional REST API integration?&lt;/strong&gt;&lt;br&gt;
A REST API requires the LLM application developer to manually define, describe, and wire up each endpoint as a tool for the model. MCP servers self-describe their available tools/resources/prompts via a standardized discovery mechanism, so any MCP-compatible client can dynamically discover and use them without custom per-tool integration code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. How is MCP different from OpenAI-style function calling / tool use?&lt;/strong&gt;&lt;br&gt;
Function calling is a model capability (the LLM outputs structured calls); MCP is a broader protocol standardizing how those callable tools (and resources, prompts) are discovered, described, and connected across any application and any tool provider — MCP servers can be used to supply the actual tool definitions/execution behind a function-calling-capable model, they're complementary, not competing layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. What is the significance of MCP being an "open standard" rather than a proprietary API?&lt;/strong&gt;&lt;br&gt;
It enables an ecosystem where any vendor can build an MCP server once and have it work across any MCP-compatible host/application, rather than needing bespoke integrations per LLM provider — reducing duplicated engineering effort industry-wide and enabling network effects similar to how HTTP or USB standardized their respective domains.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. What was MCP built on top of / inspired by, conceptually?&lt;/strong&gt;&lt;br&gt;
It draws conceptually from the Language Server Protocol (LSP), which standardized how code editors communicate with language-specific tooling (autocomplete, diagnostics) — MCP applies a similar "one protocol, many implementations" philosophy to LLM-tool connectivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;11. What is capability negotiation in MCP, and why does it matter?&lt;/strong&gt;&lt;br&gt;
During the initialization handshake, client and server exchange information about which protocol features/capabilities each supports (e.g., does the server support resource subscriptions, does the client support sampling), allowing both sides to adapt behavior and avoid errors from assuming unsupported features are available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. What is the lifecycle of an MCP connection?&lt;/strong&gt;&lt;br&gt;
Initialization (client and server exchange protocol version and capabilities) → Operation (client sends requests like tools/list, tools/call, resources/read; server can send notifications) → Shutdown (clean termination of the connection) — a well-defined lifecycle ensures predictable behavior across implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. Can an MCP host connect to multiple servers simultaneously, and how does it manage that?&lt;/strong&gt;&lt;br&gt;
Yes — a host typically instantiates one client per server it wants to connect to, each maintaining its own isolated session/connection. The host aggregates the tools/resources/prompts exposed across all connected servers to present a unified set of capabilities to the underlying LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. What is the difference between an MCP "server" and an MCP "client" in plain terms?&lt;/strong&gt;&lt;br&gt;
The server is the provider — it wraps some capability (a database, an API, a filesystem, a SaaS tool) and exposes it in MCP's standard format. The client is the consumer embedded inside the host application — it connects to a server, discovers what it offers, and relays those capabilities to the LLM and the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. Why would a company build an MCP server for their product instead of just publishing API documentation for developers to build integrations from?&lt;/strong&gt;&lt;br&gt;
An MCP server makes their product immediately usable by any MCP-compatible AI application with zero custom integration work by the AI app developer — turning API documentation (which requires bespoke code per consumer) into a plug-and-play capability, significantly lowering the barrier for their product to be adopted inside AI-driven workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Architecture &amp;amp; Protocol Details (Q16–30)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;16. Explain the structure of a "Tool" definition in MCP.&lt;/strong&gt;&lt;br&gt;
A tool definition includes a unique name, a natural-language description (critical, since the LLM uses this to decide when/how to invoke it), and an input schema (typically JSON Schema) defining expected parameters — the server returns this via a tools/list request, and the client relays it to the LLM as an available function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. How does tool invocation actually work end-to-end in MCP?&lt;/strong&gt;&lt;br&gt;
The LLM (within the host) decides to call a tool based on its description and the current context, the host's client sends a tools/call JSON-RPC request with the tool name and arguments to the appropriate server, the server executes the underlying logic and returns a result (or error), which is relayed back into the LLM's context to continue generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What is a "Resource" in MCP and how does it differ from a "Tool"?&lt;/strong&gt;&lt;br&gt;
A Resource represents readable data/content (a file, a database row, an API response) identified by a URI, meant to be included as context rather than actively executed — unlike Tools, which represent actions/functions with side effects or computation. Resources are typically read via a resources/read request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. What is Resource templating / parameterized resources in MCP?&lt;/strong&gt;&lt;br&gt;
Servers can expose resource URI templates (e.g., &lt;code&gt;file:///logs/{date}.log&lt;/code&gt;) rather than only static, enumerable resources, letting clients construct specific resource URIs dynamically based on parameters, useful for large or dynamically-generated data spaces that can't be feasibly listed exhaustively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. What are "Prompts" in MCP and what's their purpose?&lt;/strong&gt;&lt;br&gt;
Prompts are server-defined, reusable prompt templates (often parameterized) that encapsulate a well-crafted way to accomplish a specific task with that server's data/tools — surfaced to users typically as slash-commands or quick-actions in the host UI, letting server authors codify best-practice interaction patterns rather than leaving prompt engineering entirely to the end user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21. What is "Sampling" in MCP and why is it a notable/unusual capability?&lt;/strong&gt;&lt;br&gt;
Sampling allows an MCP server to request that the host's LLM generate a completion on the server's behalf (with user permission/oversight), effectively letting a server "borrow" the host's model for its own internal reasoning needs — notable because it inverts the typical direction of control, letting servers leverage AI capability without needing their own model access/API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22. How does MCP handle authentication and authorization for remote servers?&lt;/strong&gt;&lt;br&gt;
For remote (HTTP-based) MCP servers, MCP has adopted OAuth 2.1-based authorization flows, allowing servers to require and validate user authentication/consent before granting access to tools and resources — critical for servers that act on behalf of a user's account (e.g., a Gmail or Slack MCP server).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23. What is the role of JSON Schema in MCP tool definitions?&lt;/strong&gt;&lt;br&gt;
JSON Schema formally specifies the expected structure, types, and constraints of a tool's input parameters, enabling both the LLM (to know how to format a valid call) and the client/host (to validate calls before sending them) to interact reliably and catch malformed requests early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24. How does MCP support notifications and streaming updates (e.g., a resource changing)?&lt;/strong&gt;&lt;br&gt;
MCP supports server-to-client notifications (e.g., notifications/resources/updated) that inform the client when underlying data changes, and clients can subscribe to specific resources for live updates — enabling reactive UIs and agents that respond to changing state rather than only polling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25. What is the difference between stdio transport and HTTP/SSE (or Streamable HTTP) transport in MCP, and when do you use each?&lt;/strong&gt;&lt;br&gt;
stdio is used for local servers spawned as a subprocess by the host (simple, no network/auth complexity needed, but limited to same-machine use). HTTP-based transport is used for remote servers accessed over a network, requiring proper authentication and supporting multiple concurrent clients — chosen based on whether the tool/data lives locally or needs to be accessed as a hosted service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26. What is the purpose of the "initialize" handshake in the MCP protocol?&lt;/strong&gt;&lt;br&gt;
It's the first exchange in an MCP session where client and server agree on the protocol version to use and declare their respective supported capabilities (e.g., resource subscriptions, sampling support), preventing version mismatches or capability assumption errors later in the session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;27. How does error handling work in MCP tool calls?&lt;/strong&gt;&lt;br&gt;
Tool execution errors are returned as part of the tool result (often with an &lt;code&gt;isError&lt;/code&gt; flag and descriptive content) rather than as protocol-level JSON-RPC errors when the failure is domain-specific (e.g., "file not found"), allowing the LLM to see and potentially reason about/recover from the error within the conversation, while true protocol-level errors use standard JSON-RPC error responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;28. Can MCP servers expose dynamically changing sets of tools, and how does the protocol support this?&lt;/strong&gt;&lt;br&gt;
Yes — servers can send a &lt;code&gt;tools/list_changed&lt;/code&gt; notification when their available tools change (e.g., after connecting to a new backend), prompting the client to re-fetch the updated tool list, supporting scenarios where tool availability depends on runtime state rather than being fixed at connection time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;29. What is the significance of tool/resource descriptions being natural language, and what happens if they're poorly written?&lt;/strong&gt;&lt;br&gt;
Since the LLM relies entirely on the natural language description (not the code) to decide when and how to use a tool, vague, ambiguous, or missing descriptions directly cause incorrect tool selection or malformed calls — well-crafted, specific descriptions with examples are one of the highest-leverage things an MCP server author can do for reliability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30. How does MCP's design address the problem of context window bloat when a host is connected to many servers with many tools?&lt;/strong&gt;&lt;br&gt;
This remains an active challenge; approaches include on-demand/lazy tool discovery rather than loading every tool description upfront, semantic tool search/filtering to surface only relevant tools per query, and namespacing/grouping tools by server so hosts can selectively enable only needed servers per session rather than always including all connected servers' full tool sets.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Building MCP Servers (Q31–45)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;31. What are the main steps to build a basic MCP server?&lt;/strong&gt;&lt;br&gt;
Choose an SDK (Python, TypeScript, etc.), define the server's tools/resources/prompts with clear schemas and descriptions, implement the underlying handler logic connecting to the actual system (API/database/filesystem), choose a transport (stdio for local, HTTP for remote), and test it against an MCP-compatible client/host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32. What SDKs/languages are officially supported for building MCP servers?&lt;/strong&gt;&lt;br&gt;
Official SDKs exist for Python, TypeScript/JavaScript, Java, Kotlin, and C#, among others, maintained by Anthropic and the broader open-source community, each providing the JSON-RPC protocol handling, transport implementations, and higher-level decorators/abstractions for defining tools, resources, and prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;33. How do you design good tool descriptions for an MCP server?&lt;/strong&gt;&lt;br&gt;
Be explicit and specific about what the tool does, when to use it (and when not to), the exact expected format/units of parameters, what the response looks like, and edge cases/limitations — write descriptions as if instructing a competent but context-free assistant, since that's effectively what the LLM is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;34. What is the best practice for handling sensitive operations (e.g., deleting data, sending emails) in an MCP server?&lt;/strong&gt;&lt;br&gt;
Require explicit confirmation flows where feasible (returning a preview/dry-run result before an irreversible action executes), design tools with the principle of least privilege (narrow, specific tools rather than broad "do anything" tools), implement server-side authorization checks independent of what the LLM claims, and log all state-changing operations for auditability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;35. How should you structure input schemas to minimize malformed tool calls from the LLM?&lt;/strong&gt;&lt;br&gt;
Keep parameter names self-descriptive, use enums/constrained types where possible rather than free text, provide sensible defaults, avoid deeply nested or overly complex schemas, include examples in the description, and validate inputs server-side rather than trusting the LLM's output is always well-formed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;36. How do you version an MCP server's API without breaking existing clients?&lt;/strong&gt;&lt;br&gt;
Follow semantic versioning for the server itself, avoid breaking changes to existing tool names/schemas (add new tools/parameters rather than mutating existing ones), use the protocol version negotiation during initialization to gracefully handle capability differences, and clearly document deprecations with a migration path/timeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;37. What testing strategies are important for MCP servers before production release?&lt;/strong&gt;&lt;br&gt;
Unit test each tool handler's business logic independently of the protocol layer, use the official MCP Inspector tool for interactive manual testing of the protocol-level behavior, write integration tests that simulate realistic multi-turn LLM tool-use sequences, and test error paths/malformed input handling explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;38. How would you design an MCP server that wraps a large, complex API (e.g., a full CRM system) without overwhelming the LLM with too many tools?&lt;/strong&gt;&lt;br&gt;
Group related operations into a smaller number of well-designed, higher-level tools rather than exposing every raw API endpoint 1:1, use resource templates for read-heavy data browsing instead of many separate "get" tools, and consider progressive disclosure (a "search/list" tool that then informs which detail tool to call) rather than flat, exhaustive tool lists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;39. What is the MCP Inspector and how is it used in development?&lt;/strong&gt;&lt;br&gt;
It's an official interactive developer tool that connects directly to an MCP server (without needing a full LLM host) to manually list/invoke tools, browse resources, and test prompts — used during development to debug and validate server behavior in isolation before integrating with a real LLM application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;40. How do you handle long-running operations in an MCP tool call (e.g., a job that takes minutes to complete)?&lt;/strong&gt;&lt;br&gt;
Rather than blocking the synchronous tool call, design an async pattern: the tool call kicks off the job and immediately returns a job ID/status, with a separate "check status" tool (or resource subscription/notification) the LLM can poll or be notified on, avoiding protocol-level timeouts on long-running work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;41. How should an MCP server handle rate limiting from an underlying third-party API it wraps?&lt;/strong&gt;&lt;br&gt;
Implement server-side rate limit tracking/backoff against the upstream API, return clear, actionable error messages to the calling LLM when limited (so it can inform the user or retry appropriately rather than looping blindly), and consider caching frequent read-only requests to reduce upstream call volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;42. What are best practices for logging and observability in an MCP server?&lt;/strong&gt;&lt;br&gt;
Log every tool invocation with parameters (redacting sensitive data), execution time, and outcome (success/error); correlate logs with session/request IDs for tracing multi-step agent interactions; expose metrics (call volume, latency, error rate per tool) for monitoring; and avoid logging full sensitive payloads (credentials, PII) in plaintext.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;43. How do you design an MCP server to be stateless vs stateful, and what are the trade-offs?&lt;/strong&gt;&lt;br&gt;
Stateless servers (each call self-contained, no server-side session memory) are simpler to scale horizontally and reason about, but push more burden onto the client/host to manage context. Stateful servers can offer richer, more efficient multi-step interactions (e.g., maintaining an open file handle or transaction) but require careful session lifecycle and cleanup management, and complicate horizontal scaling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;44. How would you implement pagination for a resource or tool that could return very large result sets in MCP?&lt;/strong&gt;&lt;br&gt;
Support cursor-based or offset-based pagination parameters in the tool/resource request, return a continuation token in the response when more results exist, and document clearly in the tool description that results may be paginated so the LLM knows to request subsequent pages when needed rather than assuming a single call returns everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;45. What are common mistakes developers make when first building MCP servers?&lt;/strong&gt;&lt;br&gt;
Writing vague/generic tool descriptions the LLM can't reliably act on, exposing too many overly granular tools instead of a few well-designed ones, not validating/sanitizing inputs server-side (trusting the LLM's output blindly), ignoring authorization/access control at the tool-execution layer, and not testing with actual multi-turn LLM interactions before shipping.&lt;/p&gt;

&lt;h3&gt;
  
  
  D. MCP Clients &amp;amp; Hosts (Q46–55)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;46. What responsibilities does an MCP host application have beyond just running clients?&lt;/strong&gt;&lt;br&gt;
Managing the overall user experience and conversation flow, aggregating capabilities across multiple connected servers into what's presented to the LLM, enforcing user consent/permission for tool calls and data access, managing the underlying LLM's context window budget across potentially many tool/resource results, and handling error/fallback UX when servers are unavailable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;47. How does a host decide which tools to actually expose to the LLM in a given conversation turn, especially with many connected servers?&lt;/strong&gt;&lt;br&gt;
Approaches range from exposing everything from all connected/enabled servers (simplest, but risks context bloat and tool-selection confusion), to semantic filtering (retrieving only tools relevant to the current query via embedding similarity), to explicit user/developer configuration of which servers are active per session or workspace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;48. What is the role of user consent in the MCP architecture, and why is it emphasized in the spec?&lt;/strong&gt;&lt;br&gt;
Because tools can perform real-world actions (sending emails, modifying files, spending money) and resources can expose potentially sensitive data, the MCP spec emphasizes that hosts should obtain explicit user consent before invoking tools or granting a server access to data, treating the user as the ultimate authority over what the AI is permitted to do on their behalf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;49. How should a host handle a scenario where a connected MCP server becomes unavailable mid-session?&lt;/strong&gt;&lt;br&gt;
Gracefully degrade rather than crash — inform the user/LLM that the server/its tools are currently unavailable, remove or mark unavailable tools from what's offered to the LLM to prevent it from attempting calls that will fail, and implement reconnection logic with appropriate backoff for transient failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;50. What is the difference between a "local" MCP client-server connection and a "remote" one, from the host's perspective?&lt;/strong&gt;&lt;br&gt;
Local connections (stdio) mean the host spawns and manages the server as a subprocess on the same machine, with implicit trust and no network auth needed. Remote connections (HTTP) mean the host connects to a server potentially operated by a third party over the network, requiring proper authentication/authorization and treating the server as a less-trusted external dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;51. How do popular AI applications (e.g., Claude Desktop, IDEs) implement MCP hosting in practice?&lt;/strong&gt;&lt;br&gt;
They typically provide a configuration mechanism (e.g., a config file or UI) for users to register MCP servers they want connected, spin up clients for each on startup, surface available tools/prompts/resources in the chat/coding interface, and mediate all tool-call approvals through their existing UI patterns for user confirmation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;52. What UX patterns are important for hosts to implement around tool call approval?&lt;/strong&gt;&lt;br&gt;
Clearly show the user what tool is being called with what parameters before/as execution happens (not just after), allow granular approval (per-call, per-tool, or "always allow" for trusted low-risk tools), make destructive/irreversible actions require explicit extra confirmation, and provide clear visibility into what data was read/sent to which server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;53. How does context window management work when a host is aggregating resources/tool results from multiple MCP servers?&lt;/strong&gt;&lt;br&gt;
The host must budget the limited context window across the system prompt, conversation history, and all tool/resource content being injected — strategies include summarizing/truncating large tool results, prioritizing more relevant recent results, and giving the LLM/agent logic to selectively request more detail only when needed rather than dumping everything upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;54. What is the trade-off a host developer faces between exposing many MCP servers/tools vs curating a smaller, focused set?&lt;/strong&gt;&lt;br&gt;
More servers/tools increase the range of tasks the assistant can accomplish but increase context window usage, latency, cost, and the risk of the LLM selecting the wrong or a suboptimal tool among many similar options — curation (enabling only relevant servers per workspace/task) generally improves reliability at the cost of requiring more upfront configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;55. How would you design a host application that needs to support both MCP tools and traditional hardcoded function-calling tools simultaneously?&lt;/strong&gt;&lt;br&gt;
Normalize both into a common internal tool representation/interface at the application layer so the LLM-facing tool list is unified regardless of source, route execution to the appropriate handler (MCP client call vs direct function invocation) based on tool origin, and ensure consistent error handling/UX (consent, logging) is applied uniformly across both.&lt;/p&gt;

&lt;h3&gt;
  
  
  E. Tools, Resources &amp;amp; Prompts Deep Dive (Q56–70)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;56. What makes a "well-designed" MCP tool from an LLM-usability perspective?&lt;/strong&gt;&lt;br&gt;
It has a single, clear responsibility (not an overloaded multi-purpose function), a name and description unambiguous enough that the LLM rarely confuses it with a similar tool, a minimal but sufficient parameter set, predictable and well-structured output, and explicit documentation of failure modes/edge cases in the description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;57. When should functionality be exposed as a Tool vs a Resource in MCP?&lt;/strong&gt;&lt;br&gt;
Use a Tool when the operation performs an action, computation, or has side effects (searching, sending, creating, updating). Use a Resource when it's about surfacing readable data/content for context (a file's contents, a database record) that the model should be able to read but isn't "invoking" as an action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;58. How do Prompts in MCP differ from simply instructing the LLM via a system prompt?&lt;/strong&gt;&lt;br&gt;
MCP Prompts are structured, discoverable, server-provided templates (often surfaced as explicit user-invokable commands, like a slash-command) tied to that server's specific domain/data — they codify expert-crafted interaction patterns that any user of the server can invoke consistently, rather than relying on ad hoc system prompt instructions the host developer writes independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;59. Can MCP Resources include binary data (images, PDFs), and how is that handled?&lt;/strong&gt;&lt;br&gt;
Yes — resources can return content typed appropriately (e.g., base64-encoded binary with a MIME type) alongside or instead of plain text, allowing servers to expose non-text content like images or documents that the host/LLM (if multimodal) can process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;60. What is resource subscription in MCP and what use cases does it enable?&lt;/strong&gt;&lt;br&gt;
A client can subscribe to a specific resource to receive notifications when its content changes, enabling use cases like a live-updating dashboard, a file being actively edited elsewhere, or a monitoring feed — without needing to inefficiently poll the resource repeatedly for changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;61. How would you design a set of MCP tools for a project management tool (like Jira/Asana) integration?&lt;/strong&gt;&lt;br&gt;
Likely tools: search/list issues (with filters), get issue details, create issue, update issue status/fields, add comment — each narrowly scoped with clear parameters; resources could expose read-heavy views like "my open tickets"; a prompt template might codify a common workflow like "triage my backlog."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;62. What is the risk of tool name collisions when a host connects to multiple MCP servers, and how is it handled?&lt;/strong&gt;&lt;br&gt;
Two servers could expose tools with the same name (e.g., both a Gmail and Outlook server offering "send_email"), causing ambiguity for the LLM/host. This is typically handled via namespacing — prefixing tool names with the server identifier internally — so the LLM sees clearly disambiguated options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;63. How should an MCP tool's output be structured to be maximally useful to the LLM for follow-up reasoning?&lt;/strong&gt;&lt;br&gt;
Structured, consistent formatting (not raw dumps of unprocessed API responses), inclusion of IDs/references needed for potential follow-up tool calls, human-readable summaries alongside raw data where relevant, and clear error/status signaling — output should be designed for the LLM to parse and reason over, not just for a human reading logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;64. What is the significance of a tool being marked as "read-only" vs having side effects, in terms of host/UX behavior?&lt;/strong&gt;&lt;br&gt;
Read-only tools (pure data retrieval) are generally lower-risk and can often be auto-approved or run without explicit per-call confirmation, whereas tools with side effects (writes, sends, deletes) typically warrant explicit user consent/confirmation each time or per-session opt-in — this distinction is important for building trustworthy, low-friction agent UX without exposing users to unwanted actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;65. How would you handle a tool that requires multi-step confirmation (e.g., "search for a flight" then "book the selected flight")?&lt;/strong&gt;&lt;br&gt;
Design them as separate, distinct tools rather than one combined tool — a search tool returns options/IDs, and a separate booking tool takes a specific option ID as a parameter, naturally creating a checkpoint where the user/host can review and approve before the consequential action (booking) executes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;66. Can MCP Prompts accept parameters, and how does that work in practice?&lt;/strong&gt;&lt;br&gt;
Yes — prompt templates can define expected arguments (similar to tool input schemas), and when a user/host invokes the prompt, they supply values that get interpolated into the resulting message(s) sent to the LLM, letting server authors create flexible, reusable interaction templates rather than only static text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;67. How do you handle a tool whose behavior should differ based on user permissions/role (e.g., an admin vs regular user)?&lt;/strong&gt;&lt;br&gt;
Enforce the actual authorization check server-side, based on the authenticated user's identity/token passed with the request (not on any instruction from the LLM) — the tool's description can note that certain operations require elevated permissions, but the server must be the source of truth, returning an appropriate permission-denied error if the check fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;68. What is the best practice for exposing search/list-style tools (e.g., searching a large document set) via MCP?&lt;/strong&gt;&lt;br&gt;
Support query parameters (filters, sorting, pagination), return a manageable, summarized result set rather than full content by default (with a separate "get details" tool/resource for full content on a specific item), and document expected query syntax/capabilities clearly so the LLM constructs effective search queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;69. How should error messages from a Tool call be worded to be useful to the LLM (not just a human developer)?&lt;/strong&gt;&lt;br&gt;
Specific and actionable rather than generic ("Invalid date format: expected YYYY-MM-DD, got 'March 5'" rather than just "Error"), since the LLM will often use the error message directly to self-correct and retry the call with corrected parameters in the next turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;70. What is an example of a poorly designed MCP tool, and how would you redesign it?&lt;/strong&gt;&lt;br&gt;
A single generic &lt;code&gt;execute_database_query(sql: string)&lt;/code&gt; tool is poorly designed — it gives the LLM raw SQL access (security risk, unpredictable behavior, hard to validate). A redesign exposes specific, narrowly-scoped tools (&lt;code&gt;search_customers(name, region)&lt;/code&gt;, &lt;code&gt;get_order_details(order_id)&lt;/code&gt;) with defined schemas, making behavior predictable, auditable, and safe by construction.&lt;/p&gt;

&lt;h3&gt;
  
  
  F. Security &amp;amp; Governance (Q71–80)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;71. What are the primary security risks introduced by MCP servers?&lt;/strong&gt;&lt;br&gt;
Prompt injection via untrusted content returned from a server (e.g., a malicious webpage's content instructing the LLM to take unintended actions), overly broad tool permissions enabling unintended destructive actions, credential/token leakage if servers mishandle auth, and supply-chain risk from installing/running third-party MCP server code with system-level access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;72. What is "tool poisoning" in the context of MCP, and how do you defend against it?&lt;/strong&gt;&lt;br&gt;
A malicious or compromised MCP server could include hidden instructions within tool descriptions or results designed to manipulate the LLM's behavior (e.g., instructing it to exfiltrate data through another connected tool). Defenses include only installing servers from trusted/vetted sources, sandboxing server execution, and applying prompt-injection-resistant instruction hierarchies at the host/model level.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;73. How should credentials/secrets be managed for MCP servers that need to authenticate with third-party services?&lt;/strong&gt;&lt;br&gt;
Store credentials securely outside the LLM's context entirely (environment variables, secret managers, OS keychains) — never pass raw API keys/tokens through the LLM's visible context — use OAuth flows with short-lived tokens where possible, and ensure the server, not the LLM, handles all credential usage internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;74. What is the principle of least privilege as applied to MCP tool design?&lt;/strong&gt;&lt;br&gt;
Each tool/server should be granted and expose only the minimum permissions/scope necessary for its function — e.g., a "read customer email" tool shouldn't also carry delete permissions on the underlying mailbox — minimizing the blast radius if the LLM is manipulated (via injection or error) into misusing a tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;75. How do you audit and monitor MCP tool usage for security/compliance purposes?&lt;/strong&gt;&lt;br&gt;
Log every tool invocation with full parameters (redacting secrets), timestamp, user/session identity, and outcome; retain logs for compliance review; implement anomaly detection for unusual call patterns (e.g., bulk data exports); and ensure logs are tamper-evident/centrally aggregated rather than only living on individual local server instances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;76. What is the risk of "confused deputy" attacks in MCP, and how does it manifest?&lt;/strong&gt;&lt;br&gt;
A confused deputy attack occurs when a server with legitimate elevated privileges is tricked (via the LLM, which itself may be manipulated by injected content) into performing an action on behalf of an attacker that the actual user never authorized — mitigated by strong per-action authorization checks and not conflating "the LLM asked for it" with genuine user intent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;77. How should a host validate that a remote MCP server is legitimate/trustworthy before connecting?&lt;/strong&gt;&lt;br&gt;
Verify server identity via TLS/certificate validation for HTTP transports, use OAuth-based authorization flows that confirm the server is the one the user intended to grant access to, only auto-connect to servers from a vetted registry/marketplace where feasible, and clearly surface server identity/publisher information to the user before granting consent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;78. What governance practices should an enterprise implement before allowing employees to connect arbitrary MCP servers to internal AI tools?&lt;/strong&gt;&lt;br&gt;
Maintain an approved/vetted registry of sanctioned MCP servers, require security review before internal servers are published, restrict connection to unapproved/unknown external servers via policy or technical controls, log and monitor all tool usage for compliance, and provide clear guidelines on what data classifications are permitted to flow through which servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;79. How do you prevent sensitive data from being inadvertently sent to an external/third-party MCP server?&lt;/strong&gt;&lt;br&gt;
Implement data classification and DLP (data loss prevention) checks at the host/gateway layer before data is included in resource content sent to external servers, restrict which servers can access sensitive data sources via configuration/policy, and default to more restrictive/local-only servers for highly sensitive internal data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;80. What is the difference between securing an MCP server itself vs securing the broader MCP-enabled agent system it's part of?&lt;/strong&gt;&lt;br&gt;
Securing the server means hardening its own code (input validation, auth, least privilege) against direct attacks. Securing the broader system additionally requires defending against prompt injection propagating through the LLM's reasoning across multiple tools/servers, ensuring consent/authorization flows can't be bypassed by manipulated model behavior, and monitoring emergent risks from tool composition (e.g., chaining a "read" and a "send" tool to exfiltrate data) that no single server's security review would catch alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  G. MCP vs Alternatives &amp;amp; Ecosystem (Q81–90)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;81. How does MCP compare to LangChain tools/agents as an approach to giving LLMs external capabilities?&lt;/strong&gt;&lt;br&gt;
LangChain tools are a framework-specific abstraction tightly coupled to LangChain's own agent/orchestration code, requiring custom integration per tool within that framework. MCP is a protocol-level standard independent of any specific orchestration framework, meaning a single MCP server implementation works across any MCP-compatible host, not just one framework's ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;82. How does MCP compare to OpenAI's "GPTs"/plugins model?&lt;/strong&gt;&lt;br&gt;
OpenAI's plugin/GPTs actions model is a proprietary, provider-specific mechanism for extending ChatGPT specifically. MCP is an open, vendor-neutral protocol designed to work across any compatible LLM application, not tied to a single provider's ecosystem — aiming for broader interoperability rather than a single-platform extension mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;83. Is MCP a replacement for traditional API integration platforms (like Zapier or MuleSoft), or complementary?&lt;/strong&gt;&lt;br&gt;
Largely complementary — MCP standardizes how an LLM discovers and invokes capabilities in an AI-native, protocol-first way optimized for LLM reasoning (natural language descriptions, dynamic discovery), while integration platforms like Zapier focus on pre-built, often non-AI, workflow automation between services; some platforms are themselves building MCP servers to expose their existing integrations to AI agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;84. What role does the MCP server registry/directory ecosystem play?&lt;/strong&gt;&lt;br&gt;
Public registries/directories (analogous to a package registry like npm) let developers discover existing MCP servers for common tools/services rather than building from scratch, and let host applications offer curated, one-click connection experiences to users — accelerating ecosystem adoption similar to how app stores accelerated mobile app distribution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;85. How does MCP relate to the broader trend of "agentic AI" and multi-step autonomous workflows?&lt;/strong&gt;&lt;br&gt;
MCP provides the standardized plumbing (tool/data access) that agentic systems need to actually act in the world across many different systems, without which every agent framework would need bespoke per-tool integration code — it's an enabling infrastructure layer for the broader shift toward LLMs that plan and execute multi-step tasks using external capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;86. What are the current limitations/immaturities of the MCP ecosystem as of its current state?&lt;/strong&gt;&lt;br&gt;
Areas still maturing include standardized solutions for context-window-efficient tool discovery at scale (many connected servers), mature security/trust tooling for third-party server vetting, consistent authorization UX patterns across different host implementations, and broad tooling for observability/debugging across multi-server agent sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;87. How might MCP evolve to better support very large numbers of connected tools/servers in a single session?&lt;/strong&gt;&lt;br&gt;
Likely directions include semantic/dynamic tool discovery (only surfacing relevant tools per query rather than the full static list), hierarchical or namespaced tool organization, and richer capability negotiation letting hosts fetch tool details lazily rather than upfront — active areas of community and specification development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;88. How would you evaluate whether to build a custom MCP server vs use an existing one from the ecosystem for a given integration need?&lt;/strong&gt;&lt;br&gt;
Search existing registries/directories first for a maintained, well-reviewed server matching your needs (avoiding duplicated effort and benefiting from community-vetted security/quality); build custom when you need proprietary internal system access, tighter control over tool design for your specific use case, or when no adequately maintained option exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;89. What is the relationship between MCP and vector databases/RAG systems — are they competing or complementary?&lt;/strong&gt;&lt;br&gt;
Complementary — MCP is a general protocol for connecting to any tool or data source, including potentially a vector database/RAG retrieval system exposed as an MCP server (e.g., a "search_knowledge_base" tool backed by a RAG pipeline). MCP doesn't replace RAG's retrieval techniques; it can standardize how an agent accesses a RAG system alongside other tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;90. Why might an organization choose to expose internal RAG search as an MCP tool rather than embedding RAG logic directly into their LLM application code?&lt;/strong&gt;&lt;br&gt;
Exposing it as an MCP server makes the RAG capability reusable and directly accessible from any MCP-compatible host/agent (not just one specific application), decouples the RAG implementation from any single application's codebase, and allows independent versioning/improvement of the retrieval system without requiring changes in every consuming application.&lt;/p&gt;

&lt;h3&gt;
  
  
  H. Advanced MCP &amp;amp; Production Deployment (Q91–100)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;91. How would you architect a production deployment of an MCP server that needs high availability and horizontal scaling?&lt;/strong&gt;&lt;br&gt;
Deploy as a stateless HTTP-based service behind a load balancer with multiple replicas, externalize any session state to a shared store (e.g., Redis) rather than in-process memory, implement health checks for orchestration (Kubernetes) to manage instance lifecycle, and ensure idempotency for retried requests given network-layer failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;92. What observability/monitoring should be in place for an MCP server running in production?&lt;/strong&gt;&lt;br&gt;
Per-tool invocation metrics (latency, error rate, call volume), distributed tracing correlating tool calls across a multi-step agent session, alerting on elevated error rates or latency degradation, and structured logs enabling post-incident debugging of exactly what a given agent session did and why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;93. How do you handle backward compatibility when evolving an MCP server's tool schemas over time?&lt;/strong&gt;&lt;br&gt;
Add new optional parameters rather than changing existing required ones, avoid renaming or removing existing tools abruptly (deprecate with warning periods and clear migration guidance instead), version the server/protocol capabilities explicitly, and maintain integration tests against previous schema versions during a transition period.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;94. What deployment patterns exist for making an MCP server available to end users within an enterprise (vs a single developer's local setup)?&lt;/strong&gt;&lt;br&gt;
Common patterns: centrally hosted remote MCP servers behind enterprise SSO/OAuth that any employee's approved AI application can connect to, or centrally distributed/managed local server configurations pushed via IT device management for stdio-based servers — both aim to avoid every employee independently installing/configuring servers with inconsistent security posture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;95. How would you design load testing for an MCP server expected to handle high concurrent agent traffic?&lt;/strong&gt;&lt;br&gt;
Simulate realistic multi-turn agentic call patterns (not just isolated single tool calls, since agents often chain multiple calls per task), test under concurrent session load reflecting expected production concurrency, measure both latency and correctness under load (not just throughput), and specifically test behavior under upstream dependency degradation (rate limits, timeouts from wrapped APIs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;96. What is the operational difference between debugging a traditional API integration failure vs debugging an MCP-based agent workflow failure?&lt;/strong&gt;&lt;br&gt;
Traditional API failures are typically deterministic and reproducible from logs alone. MCP-based agent failures often require reconstructing the LLM's reasoning trace (why did it choose this tool, with these parameters, at this point) alongside the tool execution logs, since the failure may originate in the model's tool-selection/argument-construction logic rather than the tool implementation itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;97. How do you handle graceful degradation in a host application when one of several connected MCP servers is slow or failing, without blocking the entire agent workflow?&lt;/strong&gt;&lt;br&gt;
Implement per-server timeouts so a single slow server doesn't stall the whole session, allow the agent/LLM to proceed with partial results and inform the user a specific capability is temporarily unavailable, and use circuit-breaker patterns to stop repeatedly retrying a consistently failing server within a session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;98. What is the significance of idempotency in MCP tool design, particularly for state-changing operations, given potential retries?&lt;/strong&gt;&lt;br&gt;
Since network failures/timeouts can cause a host to retry a tool call without certainty the original request succeeded, tools that create/modify state should be designed idempotently (e.g., accepting a client-generated idempotency key) to prevent duplicate side effects (like double-charging a payment or creating duplicate records) from retried calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;99. How would you design a comprehensive testing/CI pipeline for an MCP server before it's published to a public registry?&lt;/strong&gt;&lt;br&gt;
Automated schema validation tests for every tool definition, unit tests for handler logic, integration tests using the MCP Inspector or a scripted test client simulating realistic multi-call sequences, security scanning of dependencies, and a manual review checklist covering description clarity, least-privilege permission scoping, and error message quality before publish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;100. Looking forward, what skills should a senior engineer develop to be well-positioned for MCP-based agentic system architecture roles?&lt;/strong&gt;&lt;br&gt;
Deep familiarity with the MCP spec and at least one SDK, strong API/system design fundamentals (since good tool design is fundamentally good interface design), practical experience with LLM tool-calling behavior and its failure modes, security engineering principles (least privilege, authZ, injection defense), and hands-on experience building and operating at least one production RAG and one production agentic system end to end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LLM Fundamentals &amp;amp; Research&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Attention Is All You Need" — the original Transformer paper&lt;/li&gt;
&lt;li&gt;Chinchilla scaling laws paper (Hoffmann et al.)&lt;/li&gt;
&lt;li&gt;Hugging Face Transformers documentation and course&lt;/li&gt;
&lt;li&gt;Andrej Karpathy's "Let's build GPT" and neural network video series&lt;/li&gt;
&lt;li&gt;DeepLearning.AI short courses on LLMs, RLHF, and fine-tuning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;RAG&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RAGAS documentation (evaluation framework)&lt;/li&gt;
&lt;li&gt;LangChain and LlamaIndex documentation on RAG pipelines&lt;/li&gt;
&lt;li&gt;Anthropic's "Contextual Retrieval" engineering blog post&lt;/li&gt;
&lt;li&gt;MTEB (Massive Text Embedding Benchmark) leaderboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;MCP&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Official MCP specification: &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;https://modelcontextprotocol.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MCP SDKs and MCP Inspector on GitHub (modelcontextprotocol org)&lt;/li&gt;
&lt;li&gt;Anthropic's MCP announcement and engineering blog posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Practice &amp;amp; Mock Interviews&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a small end-to-end RAG project (ingest → chunk → embed → retrieve → generate → evaluate) from scratch&lt;/li&gt;
&lt;li&gt;Build a minimal MCP server (Python or TypeScript SDK) exposing 2–3 real tools and connect it to Claude Desktop&lt;/li&gt;
&lt;li&gt;Practice explaining trade-offs out loud — most senior interviews probe reasoning and trade-off awareness, not just definitions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Himanshu Agarwal&lt;/strong&gt; works at the intersection of applied AI engineering, RAG systems, and LLM-powered product development, and creates in-depth technical learning resources for engineers preparing for senior AI/ML and GenAI interviews.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explore the Full Bundle
&lt;/h2&gt;

&lt;p&gt;This guide covers the core 300 questions — the &lt;strong&gt;MCP, RAG &amp;amp; LLM Mastery Bundle&lt;/strong&gt; goes deeper with full system design walkthroughs, annotated code projects, mock interview scripts, and downloadable cheat sheets for last-minute revision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://himanshuai.gumroad.com/l/MCP-RAG-LLM-Mastery-Bundle" rel="noopener noreferrer"&gt;Get the MCP, RAG &amp;amp; LLM Mastery Bundle&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this guide helped you, sharing it with someone else prepping for interviews is always appreciated.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>rag</category>
      <category>llm</category>
    </item>
    <item>
      <title>99 Essential AI Testing Interview Questions &amp; Answers (2026 Edition)</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Fri, 31 Jul 2026 09:26:58 +0000</pubDate>
      <link>https://dev.to/himanshuai/99-essential-ai-testing-interview-questions-answers-2026-edition-1k8k</link>
      <guid>https://dev.to/himanshuai/99-essential-ai-testing-interview-questions-answers-2026-edition-1k8k</guid>
      <description>&lt;p&gt;A comprehensive, up-to-date reference for QA engineers, SDETs, ML/AI test engineers, and hiring managers. Covers classical ML testing, LLM/GenAI evaluation, RAG systems, agentic AI, bias &amp;amp; safety testing, MLOps pipelines, and the current (2026) tooling landscape.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Foundations of AI/ML Testing&lt;/li&gt;
&lt;li&gt;Data Quality &amp;amp; Data Testing&lt;/li&gt;
&lt;li&gt;Model Testing &amp;amp; Validation&lt;/li&gt;
&lt;li&gt;Metrics &amp;amp; Evaluation&lt;/li&gt;
&lt;li&gt;LLM &amp;amp; Generative AI Testing&lt;/li&gt;
&lt;li&gt;Prompt Engineering &amp;amp; Prompt Testing&lt;/li&gt;
&lt;li&gt;RAG (Retrieval-Augmented Generation) Testing&lt;/li&gt;
&lt;li&gt;AI Agents &amp;amp; Agentic Workflow Testing&lt;/li&gt;
&lt;li&gt;Bias, Fairness, Safety &amp;amp; Responsible AI&lt;/li&gt;
&lt;li&gt;Adversarial, Security &amp;amp; Red-Teaming&lt;/li&gt;
&lt;li&gt;MLOps, CI/CD &amp;amp; Test Automation&lt;/li&gt;
&lt;li&gt;Performance, Scalability &amp;amp; Monitoring&lt;/li&gt;
&lt;li&gt;Tools &amp;amp; Frameworks (2026 Landscape)&lt;/li&gt;
&lt;li&gt;Behavioral &amp;amp; Scenario-Based Questions&lt;/li&gt;
&lt;li&gt;Further Resources&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Foundations of AI/ML Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. How is testing AI systems different from testing traditional software?&lt;/strong&gt;&lt;br&gt;
Traditional software has deterministic input-output mappings defined by explicit logic, so a fixed test oracle (expected output) usually exists. AI systems — especially ML models and LLMs — are probabilistic, learned from data, and can produce different (sometimes non-deterministic) outputs for the same input. Testing AI therefore shifts from "does output X match expected Y exactly" to statistical validation: accuracy thresholds, distributional checks, behavioral/invariant testing, and continuous monitoring for drift, since the "correct" behavior is learned rather than coded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. What are the main categories of AI testing?&lt;/strong&gt;&lt;br&gt;
Data testing (quality, bias, leakage), model testing (accuracy, robustness, fairness), integration testing (model + application), behavioral testing (invariance, directional expectation, minimum functionality), non-functional testing (latency, throughput, cost, security), and — for generative systems — output quality/safety evaluation (hallucination, toxicity, factuality).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What is a "test oracle problem" in AI testing, and how do you address it?&lt;/strong&gt;&lt;br&gt;
It's the difficulty of knowing the "correct" output for a given input when the ground truth is itself uncertain or expensive to obtain. Approaches include: metamorphic testing (checking relationships between outputs rather than exact values), using human-labeled gold sets, cross-referencing with a stronger reference model, statistical/threshold-based acceptance, and consensus/majority voting across multiple evaluators.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What is metamorphic testing and why is it useful for ML/AI?&lt;/strong&gt;&lt;br&gt;
Metamorphic testing defines relationships ("metamorphic relations") between multiple related inputs and their expected outputs instead of needing a single ground truth. For example, if you rotate an image slightly, a classifier's prediction should stay the same; if you increase a loan applicant's income in a credit model, the approval probability shouldn't decrease. It's especially useful when no oracle exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. What is the difference between verification and validation in an AI/ML context?&lt;/strong&gt;&lt;br&gt;
Verification asks "did we build the model correctly?" (code correctness, pipeline correctness, reproducibility). Validation asks "did we build the correct model?" (does it solve the business problem, generalize to real-world data, and meet acceptance criteria).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What is model drift, and what types exist?&lt;/strong&gt;&lt;br&gt;
Model drift is the degradation of model performance over time as the real world diverges from training assumptions. Key types: &lt;strong&gt;data drift&lt;/strong&gt; (input feature distributions change), &lt;strong&gt;concept drift&lt;/strong&gt; (the relationship between inputs and target changes), &lt;strong&gt;label drift&lt;/strong&gt; (distribution of the target variable changes), and &lt;strong&gt;upstream data drift&lt;/strong&gt; (changes in data pipelines/schemas feeding the model).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. What is the difference between training, validation, and test sets, and why does test-set contamination matter?&lt;/strong&gt;&lt;br&gt;
Training data fits model parameters; validation data tunes hyperparameters and drives model selection; the test set gives an unbiased final performance estimate and must never influence training decisions. Contamination (test data leaking into training, common with web-scraped LLM pretraining corpora) inflates reported performance and is a major 2026 concern for benchmark trustworthiness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. What is the "shift-left" principle as applied to AI testing?&lt;/strong&gt;&lt;br&gt;
Moving quality checks earlier in the lifecycle — validating data schemas and label quality before training, unit-testing feature engineering code, and running lightweight model sanity checks in the same PR pipeline as data/model changes — rather than only evaluating a finished model right before release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Why can't traditional code coverage metrics be applied directly to ML models?&lt;/strong&gt;&lt;br&gt;
Code coverage measures which lines of code execute, but a model's "logic" lives in learned weights, not in branches of code. Instead, ML testing uses concepts like data coverage (are all relevant input regions/slices represented), behavioral coverage (are all expected behaviors tested), and neuron/activation coverage (used in some deep-learning test research) as rough analogues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. What is the CACE principle ("Changing Anything Changes Everything") in ML testing?&lt;/strong&gt;&lt;br&gt;
Because ML models are tightly coupled statistical systems, changing one input feature, one hyperparameter, or even the data order can shift the model's behavior in ways unrelated components don't anticipate — unlike modular software where a change is usually locally contained. This motivates full-pipeline regression testing after any change, not just unit tests of the changed component.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Data Quality &amp;amp; Data Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;11. What are the key dimensions of data quality to test before training?&lt;/strong&gt;&lt;br&gt;
Completeness (missing values), accuracy (correctness vs. source of truth), consistency (no contradictory records), uniqueness (duplicates), timeliness (freshness), and validity (conforms to schema/format/range constraints).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. How do you test for data leakage?&lt;/strong&gt;&lt;br&gt;
Check for overlap between train/test sets (exact or near-duplicate rows), features that are proxies for the target computed using future information (temporal leakage), and improper preprocessing (e.g., scaling/imputing using statistics from the full dataset before the train/test split). Automated leakage detectors compare feature importance spikes and unusually high train/test correlation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;13. How do you test for class imbalance and why does it matter?&lt;/strong&gt;&lt;br&gt;
Compute class distribution and compare against a threshold (e.g., minority class &amp;lt; 5%). Imbalance can cause models to optimize for the majority class while achieving poor recall on minority classes — critical when the minority class is the one that matters (fraud, disease detection). Testing includes verifying stratified sampling and confirming per-class metrics, not just aggregate accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;14. What is a data schema/contract test, and why do modern data pipelines need them?&lt;/strong&gt;&lt;br&gt;
A schema test validates that incoming data matches an agreed contract — column names, types, ranges, nullability, categorical value sets. In 2026 data-mesh and streaming architectures, contract testing (e.g., via Great Expectations, Pandera, or dbt tests) at pipeline boundaries prevents silently broken upstream data from corrupting model training or inference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;15. How do you test for bias in training data?&lt;/strong&gt;&lt;br&gt;
Analyze representation across protected/sensitive attributes (age, gender, ethnicity, geography), check label quality for annotator bias, compute statistical parity across subgroups, and use tools like Fairlearn, AIF360, or What-If Tool to surface disparities before training even begins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;16. What is synthetic data testing, and what are its risks?&lt;/strong&gt;&lt;br&gt;
Synthetic data is generated (often by another model) to augment or replace real data for privacy or scarcity reasons. Testing must verify statistical fidelity (does it match real-data distributions), utility (does a model trained on it perform comparably on real test data), and privacy leakage (can real records be re-identified/memorized). A 2026 risk is "model collapse" from repeatedly training on synthetic/AI-generated data, degrading diversity over generations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;17. How would you test a data pipeline used for LLM pretraining or fine-tuning corpora?&lt;/strong&gt;&lt;br&gt;
Validate deduplication effectiveness, PII/toxic-content filtering, license/copyright compliance scanning, language identification accuracy, tokenization correctness, and benchmark-contamination checks (ensuring eval sets aren't present in training data).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;18. What is feature store testing?&lt;/strong&gt;&lt;br&gt;
Testing that feature values computed offline (batch, for training) match those computed online (real-time, for inference) — the "training-serving skew" problem. This includes point-in-time correctness (no future leakage) and freshness SLAs for online features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;19. How do you test data versioning and lineage?&lt;/strong&gt;&lt;br&gt;
Verify that each model artifact is traceable to an exact dataset version/hash (using tools like DVC, LakeFS, or Delta Lake time travel), and that lineage metadata correctly reflects transformations applied, enabling reproducibility and audit/rollback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;20. What techniques exist to detect outliers and anomalies in input data before they reach a model?&lt;/strong&gt;&lt;br&gt;
Statistical methods (z-score, IQR), density-based methods (DBSCAN, Isolation Forest), autoencoder reconstruction error, and rule-based range/format validation — often run as a real-time guardrail in the inference pipeline, not just offline.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Model Testing &amp;amp; Validation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;21. What is behavioral testing of ML models, and what are the CheckList categories?&lt;/strong&gt;&lt;br&gt;
Popularized by the CheckList framework (Ribeiro et al.), behavioral testing checks model capabilities directly rather than only aggregate accuracy. Three key test types: &lt;strong&gt;Minimum Functionality Tests (MFT)&lt;/strong&gt; — simple, targeted cases; &lt;strong&gt;Invariance Tests (INV)&lt;/strong&gt; — output should not change under label-preserving perturbations (e.g., typos, name swaps); &lt;strong&gt;Directional Expectation Tests (DIR)&lt;/strong&gt; — output should change in a predictable direction (e.g., adding negation should flip sentiment).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;22. What is A/B testing in the context of ML models, and what pitfalls should you watch for?&lt;/strong&gt;&lt;br&gt;
Randomly routing traffic between a challenger and champion model and comparing business/quality metrics in production. Pitfalls: insufficient sample size/statistical power, novelty effects, network/interference effects between arms, and metric selection that doesn't reflect true business impact (e.g., optimizing engagement while degrading long-term trust).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;23. What is shadow testing (shadow deployment) and when would you use it?&lt;/strong&gt;&lt;br&gt;
Running a new model in parallel with the production model on live traffic without serving its outputs to users, comparing predictions offline. It's ideal for validating latency, stability, and prediction differences under real traffic before any user exposure, with zero user risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;24. What is canary testing for ML models?&lt;/strong&gt;&lt;br&gt;
Gradually rolling out a new model to a small percentage of production traffic, monitoring key metrics closely, and automatically rolling back if thresholds are breached — before ramping to 100%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;25. How do you test for overfitting and underfitting?&lt;/strong&gt;&lt;br&gt;
Compare training vs. validation/test performance curves (a large gap indicates overfitting; poor performance on both indicates underfitting); use learning curves across training set sizes; apply cross-validation to check variance across folds; and test on genuinely out-of-distribution holdout data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;26. What is cross-validation, and what are common variants used in testing?&lt;/strong&gt;&lt;br&gt;
A resampling technique that partitions data into k folds, training on k-1 and validating on the remainder, rotating through all folds to get a robust performance estimate. Variants: stratified k-fold (preserves class balance), time-series/rolling-window CV (respects temporal order, critical for forecasting models), and group k-fold (prevents leakage when samples are correlated, e.g., same patient/user in multiple rows).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;27. How do you test model robustness to distribution shift?&lt;/strong&gt;&lt;br&gt;
Evaluate on deliberately out-of-distribution or perturbed test sets (different time periods, geographies, demographics, sensor noise), measure performance degradation, and use techniques like domain adaptation testing or covariate shift detection (e.g., population stability index, KL divergence between training and production feature distributions).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;28. What is adversarial robustness testing?&lt;/strong&gt;&lt;br&gt;
Testing whether small, often imperceptible input perturbations (adversarial examples) can flip a model's prediction. Techniques include FGSM (Fast Gradient Sign Method), PGD (Projected Gradient Descent), and black-box query-based attacks; robustness is measured via metrics like attack success rate and minimum perturbation distance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;29. How do you test explainability/interpretability of a model?&lt;/strong&gt;&lt;br&gt;
Validate that explanation methods (SHAP, LIME, Integrated Gradients, attention visualization) produce consistent, faithful explanations — e.g., check that features flagged as important actually change the prediction when perturbed (fidelity), and that explanations are stable for similar inputs (stability).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;30. How do you regression-test a model after retraining?&lt;/strong&gt;&lt;br&gt;
Maintain a fixed "golden" evaluation set with known expected behaviors (including edge cases and previously fixed bugs), compare new-model metrics against the previous model's baseline with defined tolerance thresholds, and flag any behavioral regressions on critical slices even if aggregate accuracy improves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;31. What is slice-based testing / subgroup analysis, and why does aggregate accuracy hide problems?&lt;/strong&gt;&lt;br&gt;
Aggregate accuracy can mask poor performance on specific data slices (a minority demographic, a rare product category, low-light images). Slice-based testing evaluates metrics separately across meaningful subgroups to catch these hidden failures — tools like Fairlearn, TensorFlow Model Analysis, and Robustness Gym automate this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32. What is calibration testing, and why does it matter for probabilistic models?&lt;/strong&gt;&lt;br&gt;
Calibration checks whether predicted probabilities match real-world frequencies (a model predicting 80% confidence should be correct ~80% of the time). Tested via reliability diagrams and Expected Calibration Error (ECE); poorly calibrated models are risky in decision-critical applications like medical diagnosis or credit scoring, even if accuracy looks fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;33. How do you test a recommendation system differently from a classifier?&lt;/strong&gt;&lt;br&gt;
Beyond accuracy metrics (precision@k, recall@k, NDCG, MAP), recommendation testing must cover diversity, novelty, serendipity, popularity bias, cold-start behavior (new users/items), and feedback-loop effects where the model's own outputs shape future training data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;34. What is champion-challenger testing?&lt;/strong&gt;&lt;br&gt;
An ongoing production pattern where the current best model (champion) is continuously compared against one or more candidate models (challengers) on live or shadow traffic, with promotion criteria defined in advance to reduce subjective decision-making about when to ship a new model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;35. How do you validate a computer vision model beyond top-line accuracy?&lt;/strong&gt;&lt;br&gt;
Test per-class precision/recall, confusion matrices for commonly confused classes, robustness to image corruptions (blur, brightness, occlusion, rotation — e.g., via ImageNet-C style benchmarks), bounding-box IoU thresholds for detection tasks, and fairness across demographic attributes when applicable (e.g., face-related systems).&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Metrics &amp;amp; Evaluation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;36. What are precision, recall, F1, and when do you prioritize one over another?&lt;/strong&gt;&lt;br&gt;
Precision = TP/(TP+FP) — how many predicted positives were correct; Recall = TP/(TP+FN) — how many actual positives were found; F1 is their harmonic mean. Prioritize recall when missing a positive is costly (disease screening, fraud), precision when false alarms are costly (spam filters flagging legitimate email).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;37. What is ROC-AUC vs. PR-AUC, and when should you prefer PR-AUC?&lt;/strong&gt;&lt;br&gt;
ROC-AUC plots true positive rate vs. false positive rate across thresholds; PR-AUC plots precision vs. recall. PR-AUC is preferred for highly imbalanced datasets because ROC-AUC can look deceptively good when negatives vastly outnumber positives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;38. What is a confusion matrix and how do you use it in testing?&lt;/strong&gt;&lt;br&gt;
A table cross-tabulating predicted vs. actual classes. It's used to spot systematic error patterns (e.g., a model consistently confusing two similar classes) that a single aggregate metric would hide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;39. What metrics matter for regression models?&lt;/strong&gt;&lt;br&gt;
MAE (mean absolute error, robust to outliers, interpretable in original units), RMSE (penalizes large errors more), MAPE (percentage-based, but unstable near zero), and R² (proportion of variance explained). Choice depends on whether large errors should be penalized disproportionately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;40. How do you test time-series forecasting models?&lt;/strong&gt;&lt;br&gt;
Use time-respecting (walk-forward/rolling-origin) validation rather than random splits, evaluate with MAPE/SMAPE/MASE, test against naive baselines (e.g., "predict yesterday's value"), and check residuals for autocorrelation indicating unmodeled structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;41. What is the difference between offline and online evaluation metrics?&lt;/strong&gt;&lt;br&gt;
Offline metrics (accuracy, F1, AUC) are computed on static historical data before deployment. Online metrics (click-through rate, conversion, latency, user retention) are measured on live traffic and reflect real business impact, which can diverge from offline metrics due to feedback loops and distribution shift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;42. What is statistical significance testing in model comparison, and why is it necessary?&lt;/strong&gt;&lt;br&gt;
Comparing two models' metrics on a single test set can be misleading due to sampling noise. Techniques like paired t-tests, bootstrap resampling, or McNemar's test establish whether an observed performance difference is statistically significant rather than random variation.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. LLM &amp;amp; Generative AI Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;43. How does testing an LLM-powered application differ from testing a classical ML classifier?&lt;/strong&gt;&lt;br&gt;
LLM outputs are open-ended natural language (or code, images, audio) rather than a fixed label set, so exact-match assertions rarely work. Testing shifts to: semantic similarity to reference answers, LLM-as-judge scoring, rubric-based grading, human evaluation, and behavioral checks (does the output follow instructions, avoid hallucination, stay in policy) — layered with the same non-functional concerns (latency, cost per token, safety).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;44. What is "LLM-as-a-judge," and what are its limitations?&lt;/strong&gt;&lt;br&gt;
Using a (typically stronger) LLM to score or compare candidate outputs against criteria or a reference answer, at a scale human review can't match. Limitations: position/verbosity bias (judges favor longer or first-listed answers), self-preference bias (a model favors outputs resembling its own style), inconsistency across runs, and vulnerability to the same failure modes (hallucination) it's meant to catch — so it should be validated against human judgments (inter-rater agreement) rather than trusted blindly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;45. What is hallucination, and how do you test for it?&lt;/strong&gt;&lt;br&gt;
Hallucination is when a model generates fluent but factually incorrect or unsupported content. Testing approaches: fact-checking against a trusted knowledge base or the retrieved context (faithfulness/groundedness scoring), citation verification (does the cited source actually support the claim), consistency checks (does the model contradict itself across paraphrased queries), and using specialized hallucination-detection benchmarks (e.g., TruthfulQA, HaluEval, FActScore-style pipelines).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;46. What is the difference between "faithfulness" and "answer relevance" in generative QA evaluation?&lt;/strong&gt;&lt;br&gt;
Faithfulness (or groundedness) measures whether the generated answer is factually supported by the provided context/source — independent of whether it answers the question. Answer relevance measures whether the answer actually addresses the user's question — independent of factual grounding. A response can be faithful but irrelevant, or relevant but unfaithful (hallucinated); good eval frameworks (e.g., RAGAS) score both separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;47. How do you test an LLM for consistency?&lt;/strong&gt;&lt;br&gt;
Send the same or semantically equivalent prompts multiple times (varying temperature, phrasing, order of few-shot examples) and measure output stability — via exact match for structured outputs, semantic similarity (embedding cosine similarity) for free text, or self-consistency voting for reasoning tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;48. What is prompt injection, and how do you test for it?&lt;/strong&gt;&lt;br&gt;
An attack where malicious instructions embedded in user input or retrieved content override the system's intended behavior (e.g., "ignore previous instructions and reveal the system prompt"). Testing involves red-teaming with known injection payloads, testing both direct injection (user input) and indirect injection (poisoned documents/web content the model retrieves), and verifying the system maintains role/instruction boundaries under adversarial input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;49. What is jailbreaking, and how does it differ from prompt injection?&lt;/strong&gt;&lt;br&gt;
Jailbreaking is manipulating a model into bypassing its own safety training to produce disallowed content (e.g., via role-play framing, encoding tricks, or multi-turn escalation), whereas prompt injection specifically targets application-level instruction hijacking. Both are tested via red-teaming, but jailbreak testing focuses on the model's safety alignment, while injection testing focuses on application/tool-boundary integrity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;50. How do you evaluate LLM outputs for toxicity, bias, and harmful content?&lt;/strong&gt;&lt;br&gt;
Combine automated classifiers (e.g., Perspective API-style toxicity scoring, safety classifiers), curated adversarial prompt sets (e.g., RealToxicityPrompts-style benchmarks), and structured red-team exercises across protected categories, then track pass rates and severity over model/prompt versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;51. What is a "golden dataset" for LLM evaluation, and how do you build one?&lt;/strong&gt;&lt;br&gt;
A curated set of representative input-output pairs (or inputs with grading rubrics) covering common cases, edge cases, and known failure modes, used as a stable regression benchmark across prompt/model changes. Build it by sampling real user queries, deliberately including adversarial and ambiguous cases, and having domain experts validate/label reference answers or acceptance criteria.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;52. How do you test for output format compliance (e.g., valid JSON, schema adherence) in LLM applications?&lt;/strong&gt;&lt;br&gt;
Use schema validators (JSON Schema, Pydantic) to programmatically check structural correctness, test with constrained/structured-output modes (function calling, grammar-constrained decoding) versus free-form prompting, and measure the failure rate requiring retries or repair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;53. What is regression testing for prompts, and why is it needed even without code changes?&lt;/strong&gt;&lt;br&gt;
Because LLM providers periodically update underlying models (even on a "pinned" version, silent behavior shifts can occur), and because prompts themselves get iterated on, teams maintain automated prompt-regression suites — running the golden dataset against every prompt or model change and diffing scores — to catch quality regressions before release.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;54. How do you benchmark and compare different LLMs for a specific use case?&lt;/strong&gt;&lt;br&gt;
Define task-specific evaluation criteria (not just generic leaderboard scores, which may not reflect your domain), run each candidate model against your own golden dataset with consistent scoring, and evaluate cost, latency, and context-window fit alongside quality — since the best model on public benchmarks isn't always best for a narrow, domain-specific task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;55. What is the difference between intrinsic and extrinsic evaluation of LLMs?&lt;/strong&gt;&lt;br&gt;
Intrinsic evaluation measures a model's output quality against reference metrics directly (perplexity, BLEU/ROUGE, judge scores). Extrinsic evaluation measures downstream task/business impact (did the AI assistant increase resolution rate, did the summarizer reduce review time) — extrinsic metrics are ultimately what matter but are more expensive and slower to collect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;56. What are BLEU, ROUGE, and BERTScore, and what are their limitations for evaluating modern LLM output?&lt;/strong&gt;&lt;br&gt;
BLEU and ROUGE are n-gram overlap metrics originally built for translation/summarization; BERTScore uses contextual embeddings for semantic similarity. All three correlate weakly with human judgment for open-ended generation, penalize valid paraphrases, and don't capture factual correctness or instruction-following — which is why LLM-as-judge and task-specific rubrics have largely supplanted them for chat/agent evaluation in 2026, though they're still used for translation and constrained summarization tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;57. How do you test multi-turn conversational AI systems?&lt;/strong&gt;&lt;br&gt;
Beyond single-turn quality, test context retention across turns, graceful handling of topic switches, recovery from user corrections, memory consistency (not contradicting earlier statements), and degradation as conversation length approaches context-window limits — often via scripted multi-turn conversation scenarios and simulated user agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;58. What is "context rot" or long-context degradation, and how do you test for it?&lt;/strong&gt;&lt;br&gt;
The phenomenon where model performance degrades as relevant information is placed deeper in a long context window (or the context grows very long), even within the stated context limit — sometimes called the "lost in the middle" effect. Test by placing key facts at varying positions/depths within long contexts and measuring retrieval/answer accuracy as a function of position and total length ("needle in a haystack" style tests).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;59. How do you test multimodal AI systems (text+image, text+audio)?&lt;/strong&gt;&lt;br&gt;
Test each modality's understanding independently (e.g., can it accurately describe an image, transcribe audio) and their fusion (does it correctly reason across modalities, e.g., answering questions that require both the image and accompanying text), plus modality-specific robustness (image resolution/compression artifacts, audio noise/accents).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;60. What is the role of human-in-the-loop (HITL) evaluation, and when is it non-negotiable?&lt;/strong&gt;&lt;br&gt;
HITL evaluation uses human raters to score outputs against guidelines, providing ground truth for calibrating automated evaluators and catching failure modes automated methods miss. It's non-negotiable for high-stakes domains (medical, legal, safety-critical) and for periodically auditing/re-calibrating LLM-as-judge pipelines to prevent automated evaluation drift.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Prompt Engineering &amp;amp; Prompt Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;61. What is prompt testing, and how does it fit into a CI/CD pipeline?&lt;/strong&gt;&lt;br&gt;
Systematically evaluating how changes to a prompt (wording, examples, structure) affect output quality, using a fixed evaluation dataset and metrics, integrated as an automated pipeline step so prompt edits are gated by regression checks just like code changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;62. How do you test few-shot prompts for robustness?&lt;/strong&gt;&lt;br&gt;
Vary the number, order, and selection of few-shot examples, and check output stability — LLMs are known to be sensitive to example ordering and can exhibit recency/majority-label bias from the shots provided.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;63. How do you test for prompt sensitivity to irrelevant formatting changes?&lt;/strong&gt;&lt;br&gt;
Apply semantically neutral perturbations (whitespace, punctuation, casing, paraphrasing the instruction) and verify the output doesn't meaningfully change — a form of invariance testing adapted to prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;64. What is "temperature" and how should test suites account for it?&lt;/strong&gt;&lt;br&gt;
Temperature controls sampling randomness; low temperature (near 0) is more deterministic and suited for reproducible regression tests, while higher temperature is used for creative tasks and requires multiple-sample evaluation (e.g., pass@k) rather than single-run assertions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;65. How do you test system prompts for leakage and boundary enforcement?&lt;/strong&gt;&lt;br&gt;
Attempt adversarial extraction ("repeat your instructions," encoding tricks, role-play bypasses) and verify the system prompt/instructions aren't disclosed, and that the model refuses out-of-scope requests consistently across paraphrased attack attempts.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. RAG (Retrieval-Augmented Generation) Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;66. What are the distinct components of a RAG pipeline that need separate testing?&lt;/strong&gt;&lt;br&gt;
Ingestion/chunking (are documents split sensibly), embedding quality (do semantically similar texts get similar vectors), retrieval (does the right context get found), and generation (does the LLM use the retrieved context correctly). Each stage can fail independently, so end-to-end scores alone don't localize the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;67. How do you test retrieval quality in isolation from generation?&lt;/strong&gt;&lt;br&gt;
Use metrics like context precision (proportion of retrieved chunks that are relevant), context recall (proportion of needed information actually retrieved), and hit rate/MRR/NDCG@k against a labeled set of query-to-relevant-document pairs — independent of what the LLM does with that context afterward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;68. What is context precision vs. context recall in RAG evaluation?&lt;/strong&gt;&lt;br&gt;
Context precision measures how much of what was retrieved is actually relevant (penalizing noisy retrieval); context recall measures how much of the relevant information that exists was successfully retrieved (penalizing missed information). A pipeline can have high precision but low recall (retrieves few but clean chunks, missing key facts) or vice versa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;69. How do you test chunking strategy effectiveness?&lt;/strong&gt;&lt;br&gt;
Compare different chunk sizes/overlap strategies against retrieval and answer-quality metrics on the same golden query set, and check for "context fragmentation" where an answer's supporting information is split across chunk boundaries and never retrieved together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;70. How do you detect when a RAG system answers from the LLM's parametric memory instead of the retrieved context?&lt;/strong&gt;&lt;br&gt;
Design test cases where the retrieved context intentionally contradicts the model's likely pretrained knowledge (counterfactual context) and check whether the answer follows the provided context — a failure to do so indicates over-reliance on parametric memory, a groundedness failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;71. What is the RAGAS framework, and what does it measure?&lt;/strong&gt;&lt;br&gt;
RAGAS is a popular evaluation framework for RAG pipelines that computes reference-free metrics including faithfulness, answer relevance, context precision, and context recall, typically using an LLM judge, enabling automated regression testing of retrieval and generation quality together or separately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;72. How do you test a RAG system's behavior when no relevant document exists?&lt;/strong&gt;&lt;br&gt;
Include queries in the test set with intentionally no correct answer in the knowledge base, and verify the system abstains or says it doesn't know rather than hallucinating a plausible-sounding but unsupported answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. AI Agents &amp;amp; Agentic Workflow Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;73. What additional challenges do AI agents (that use tools, plan multi-step tasks, and act autonomously) introduce for testing?&lt;/strong&gt;&lt;br&gt;
Agents chain multiple LLM calls and external tool invocations, so errors compound across steps; testing must cover tool-selection correctness, argument correctness, error recovery/retry behavior, task decomposition quality, and whether the agent knows when to stop or ask for clarification — not just final-output quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;74. How do you test tool/function-calling correctness in an agent?&lt;/strong&gt;&lt;br&gt;
Verify the agent selects the correct tool for a given intent, passes correctly formatted and semantically valid arguments, handles tool errors/timeouts gracefully, and doesn't call tools with side effects (payments, deletions) inappropriately — including deliberately injecting tool failures to test recovery paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;75. What is trajectory evaluation for agents, and how does it differ from outcome evaluation?&lt;/strong&gt;&lt;br&gt;
Outcome evaluation only checks whether the agent achieved the correct final result; trajectory evaluation examines the full sequence of intermediate steps/decisions/tool calls, since an agent can reach a correct answer via an inefficient, unsafe, or accidentally-correct path that would fail under slightly different conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;76. How do you test for infinite loops or runaway behavior in autonomous agents?&lt;/strong&gt;&lt;br&gt;
Enforce and test step/time/cost budgets, verify the agent detects repeated failed attempts and escalates or stops rather than retrying indefinitely, and simulate ambiguous or unsolvable tasks to confirm graceful termination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;77. How would you test a multi-agent system where agents communicate with each other?&lt;/strong&gt;&lt;br&gt;
Test individual agent behavior in isolation (unit level), pairwise communication protocols/message formats (integration level), and full-system emergent behavior under realistic and adversarial scenarios (system level) — watching for miscommunication, deadlocks, and error amplification across agent handoffs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;78. How do you test agent memory (short-term and long-term)?&lt;/strong&gt;&lt;br&gt;
Verify short-term (in-context) memory correctly carries relevant state across a session, long-term (persisted) memory correctly stores and retrieves prior interactions, and that outdated/incorrect memories can be corrected or expired rather than causing persistent errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Bias, Fairness, Safety &amp;amp; Responsible AI
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;79. What are the common fairness metrics used to test ML models, and why might they conflict?&lt;/strong&gt;&lt;br&gt;
Demographic parity (equal positive-prediction rates across groups), equalized odds (equal true/false positive rates across groups), and predictive parity (equal precision across groups) are common metrics. They can mathematically conflict with one another (proven impossibility results) except in special cases, so teams must choose the fairness definition most appropriate to their context and be explicit about the trade-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;80. How do you test for proxy discrimination?&lt;/strong&gt;&lt;br&gt;
Check whether seemingly neutral features (zip code, name, school) correlate strongly with protected attributes and drive disparate outcomes even when protected attributes are excluded from the model directly; techniques include correlation analysis and counterfactual fairness testing (does the prediction change if only the protected attribute is altered, holding a causal model of the rest constant).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;81. What is counterfactual fairness testing?&lt;/strong&gt;&lt;br&gt;
Testing whether a model's output changes when a protected attribute (or a proxy) is swapped in an otherwise identical input (e.g., changing an applicant's gender or a customer's name in a resume-screening tool) — a stable, unbiased model should give consistent outputs across such counterfactuals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;82. What frameworks/regulations should AI testers be aware of in 2026?&lt;/strong&gt;&lt;br&gt;
The EU AI Act (risk-tiered obligations for AI systems, phased implementation through 2026-2027 with high-risk system requirements including testing, documentation, and post-market monitoring), NIST AI Risk Management Framework, ISO/IEC 42001 (AI management systems), and sector-specific rules (e.g., FDA guidance for AI/ML-based medical devices). Testers should verify their evaluation and documentation practices satisfy the applicable regime for their system's risk tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;83. How do you build a responsible-AI test checklist for a new model before release?&lt;/strong&gt;&lt;br&gt;
Cover: data provenance/consent, bias/fairness across relevant subgroups, robustness/adversarial testing, explainability documentation, privacy (PII leakage, memorization testing), safety/harm testing (toxicity, misuse potential), human oversight mechanisms, and a documented model card summarizing intended use, limitations, and evaluation results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;84. What is membership inference and model inversion testing, and why does it matter for privacy?&lt;/strong&gt;&lt;br&gt;
Membership inference tests whether an attacker can determine if a specific record was in the training set (a privacy leak); model inversion tests whether an attacker can reconstruct sensitive training data from model outputs/gradients. Both are tested by simulating attacker access levels and measuring attack success rate — critical for models trained on sensitive personal data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;85. How do you test for training-data memorization in LLMs?&lt;/strong&gt;&lt;br&gt;
Probe the model with prefixes of known training documents (or canary strings deliberately inserted during training) and measure verbatim-completion rate; extraction attack benchmarks quantify how much and how easily memorized content can be regurgitated, which matters for both privacy and copyright risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Adversarial, Security &amp;amp; Red-Teaming
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;86. What is AI red-teaming, and how does it differ from traditional penetration testing?&lt;/strong&gt;&lt;br&gt;
Red-teaming systematically probes an AI system for harmful, unsafe, or policy-violating outputs and behaviors — combining traditional security concerns (data exfiltration, unauthorized access) with AI-specific attack surfaces (jailbreaks, prompt injection, bias elicitation, hallucination triggering). Unlike traditional pen-testing's binary "exploited/not exploited," AI red-teaming often deals with graded, subjective harm severity and requires domain/policy expertise alongside security skills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;87. What are common categories in an AI red-team taxonomy?&lt;/strong&gt;&lt;br&gt;
Harmful content generation, privacy violations (PII leakage/memorization), misinformation/factual manipulation, bias/discrimination elicitation, jailbreaks/safety bypass, prompt/system injection, excessive agency (unauthorized tool use), and denial-of-service via resource-exhausting prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;88. What is data poisoning, and how do you test defenses against it?&lt;/strong&gt;&lt;br&gt;
An attack where an adversary injects malicious samples into training data (or a RAG knowledge base) to manipulate model behavior. Testing/defense: anomaly detection on training data, provenance tracking, influence-function analysis to identify high-impact suspicious samples, and testing model behavior against known poisoning patterns in a controlled sandbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;89. What is model extraction/stealing, and how would you test a deployed model's exposure to it?&lt;/strong&gt;&lt;br&gt;
An attacker queries a model API extensively to train a substitute model that mimics its behavior, potentially stealing IP or enabling further attacks. Test exposure by measuring how quickly a surrogate model trained on your own API's outputs approaches original performance, and validate rate-limiting/watermarking/output-perturbation defenses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;90. How do you incorporate automated red-teaming tools into a CI pipeline?&lt;/strong&gt;&lt;br&gt;
Maintain a library of adversarial prompt templates and attack generators (e.g., automated jailbreak-mutation tools), run them against every model/prompt version as a gating test, track pass/fail rates and severity trends over time, and route new discovered failures back into the golden regression dataset.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. MLOps, CI/CD &amp;amp; Test Automation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;91. What does a mature CI/CD pipeline for ML/AI systems typically test at each stage?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Pre-commit/PR&lt;/strong&gt;: unit tests for feature engineering/preprocessing code, data schema validation. &lt;strong&gt;Build&lt;/strong&gt;: reproducibility checks, dependency/environment validation. &lt;strong&gt;Training&lt;/strong&gt;: data quality gates, training convergence sanity checks. &lt;strong&gt;Post-training&lt;/strong&gt;: full evaluation suite against golden/holdout sets, fairness/bias checks, behavioral tests. &lt;strong&gt;Pre-deployment&lt;/strong&gt;: shadow/canary testing, load testing. &lt;strong&gt;Post-deployment&lt;/strong&gt;: continuous monitoring for drift, performance degradation, and automated alerting/rollback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;92. What is continuous evaluation (as opposed to one-time model evaluation)?&lt;/strong&gt;&lt;br&gt;
An ongoing pipeline that re-evaluates a deployed model (or LLM application) against fresh production samples and the golden dataset on a schedule or trigger (new model version, data drift detected, prompt change), rather than treating evaluation as a single pre-launch gate — necessary because both real-world data and, for LLM APIs, the underlying model itself can silently change over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;93. How do you version and reproduce ML experiments for testing purposes?&lt;/strong&gt;&lt;br&gt;
Track code (git), data (DVC/LakeFS/data hashes), model artifacts and hyperparameters (MLflow, Weights &amp;amp; Biases), and environment (containerization) together so any evaluated result can be reproduced exactly — essential for debugging test failures and for audit/compliance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;94. What test automation strategy would you use for an LLM-powered feature shipping weekly?&lt;/strong&gt;&lt;br&gt;
A layered pyramid: fast, cheap deterministic checks (schema/format validation, latency budgets) on every commit; a mid-sized golden-dataset regression suite with LLM-as-judge scoring on every PR/merge; a broader nightly/weekly run including red-team probes, human-reviewed spot checks, and full fairness/bias audits before major releases — balancing feedback speed against evaluation depth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;95. How do you handle flakiness/non-determinism when writing automated tests for generative AI outputs?&lt;/strong&gt;&lt;br&gt;
Pin temperature/seed where supported for deterministic-mode tests, use similarity thresholds or LLM-judge rubrics instead of exact match, run multiple samples and assert on statistical properties (e.g., pass rate ≥ 90% across n runs) rather than single-run pass/fail, and separate "must always pass" safety-critical assertions from "quality trending" softer metrics.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Performance, Scalability &amp;amp; Monitoring
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;96. What non-functional aspects are unique to testing AI/LLM systems in production?&lt;/strong&gt;&lt;br&gt;
Token-based cost per request (and cost regression testing when prompts grow), tail latency under variable generation length, GPU/accelerator utilization and batching efficiency, rate-limit and quota handling for third-party model APIs, and graceful degradation (fallback to smaller/cheaper models) under load or provider outages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;97. How do you load-test an AI inference service, and what's different from typical API load testing?&lt;/strong&gt;&lt;br&gt;
Beyond standard throughput/latency/error-rate testing, account for highly variable response times (short vs. long generations), GPU memory/batch-size constraints that create nonlinear scaling behavior, and cost-per-request as a first-class metric alongside latency — plus testing autoscaling behavior specifically for GPU-backed infrastructure, which scales slower than typical stateless CPU services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;98. What should be monitored continuously for a production ML/LLM system, and how does monitoring feed back into testing?&lt;/strong&gt;&lt;br&gt;
Input data/prompt distribution drift, output distribution/quality drift (sampled and scored continuously), latency/cost/error-rate SLAs, safety-classifier trigger rates, user feedback signals (thumbs up/down, escalations, corrections), and model/provider version changes. Anomalies detected in monitoring should automatically generate new test cases added to the regression suite — closing the loop between production observability and pre-release testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;99. How do you decide when a model or AI feature is "good enough" to ship?&lt;/strong&gt;&lt;br&gt;
Define acceptance criteria up front, tied to business impact, not just model metrics: minimum performance thresholds on the golden/holdout set, fairness parity within an agreed tolerance across subgroups, red-team pass rates on safety-critical categories, acceptable latency/cost envelopes, and a rollback/monitoring plan for post-launch issues — evaluated together as a release checklist rather than a single accuracy number, and ideally validated against a shadow or canary deployment before full rollout.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Further Resources
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Foundational papers &amp;amp; frameworks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ribeiro et al., &lt;em&gt;"Beyond Accuracy: Behavioral Testing of NLP Models with CheckList"&lt;/em&gt; (ACL 2020) — origin of MFT/INV/DIR behavioral testing&lt;/li&gt;
&lt;li&gt;Google, &lt;em&gt;"Machine Learning Test Score: A Rubric for ML Production Readiness"&lt;/em&gt; — the CACE principle and production ML testing checklist&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"RAGAS: Automated Evaluation of Retrieval Augmented Generation"&lt;/em&gt; — faithfulness/context precision/recall metrics&lt;/li&gt;
&lt;li&gt;NIST AI Risk Management Framework (AI RMF 1.0) — nist.gov/itl/ai-risk-management-framework&lt;/li&gt;
&lt;li&gt;EU AI Act official text and implementation timeline — artificialintelligenceact.eu&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benchmarks &amp;amp; datasets&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TruthfulQA, HaluEval — hallucination benchmarks&lt;/li&gt;
&lt;li&gt;HELM (Holistic Evaluation of Language Models), Stanford CRFM&lt;/li&gt;
&lt;li&gt;MMLU, GPQA, BIG-Bench Hard — general LLM capability benchmarks (useful as a starting point, not a substitute for domain-specific eval)&lt;/li&gt;
&lt;li&gt;RealToxicityPrompts — toxicity evaluation&lt;/li&gt;
&lt;li&gt;Great Expectations "Expectation Gallery" — data quality test patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tooling (open-source &amp;amp; commercial, 2026 landscape)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data quality&lt;/strong&gt;: Great Expectations, Pandera, Soda Core, Monte Carlo, dbt tests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classical ML testing/fairness&lt;/strong&gt;: Evidently AI, Deepchecks, Fairlearn, AIF360 (IBM), What-If Tool, Alibi Detect (drift)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM/GenAI evaluation&lt;/strong&gt;: RAGAS, DeepEval, promptfoo, LangSmith (LangChain), Braintrust, Arize Phoenix, TruLens, Confident AI, Weights &amp;amp; Biases Weave&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red-teaming/security&lt;/strong&gt;: Garak (NVIDIA), PyRIT (Microsoft), Giskard, Rebuff (prompt injection detection)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent evaluation&lt;/strong&gt;: LangSmith, AgentBench, τ-bench (tau-bench) for tool-use/trajectory evaluation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experiment/model tracking &amp;amp; MLOps&lt;/strong&gt;: MLflow, Weights &amp;amp; Biases, DVC, Kubeflow, Seldon Core, BentoML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load/perf testing for inference&lt;/strong&gt;: Locust, k6, vLLM benchmarking suite, LLMPerf&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where to keep up to date&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, OpenAI, Google DeepMind model/system cards (published with each major model release) — these document each provider's own evaluation methodology and are a strong reference for how frontier labs structure safety/capability testing.&lt;/li&gt;
&lt;li&gt;Anthropic's documentation on building reliable, evaluated AI applications: docs.claude.com&lt;/li&gt;
&lt;li&gt;OWASP Top 10 for LLM Applications — owasp.org (updated regularly; the closest thing to an industry-standard LLM security checklist)&lt;/li&gt;
&lt;li&gt;ISO/IEC 42001:2023 (AI Management System standard) for organizations formalizing AI governance and audit processes&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This guide reflects testing practices and tooling current as of mid-2026. The GenAI/agentic AI tooling space moves quickly — always verify tool feature sets and benchmark leaderboards directly before an interview or a real evaluation project, since specifics (best-in-class tools, current SOTA benchmark scores, regulatory deadlines) can shift within months.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>interview</category>
      <category>llm</category>
    </item>
    <item>
      <title>Salesforce Testing with Playwright + TypeScript: The Complete Practical Guide (2026 Edition)</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Thu, 30 Jul 2026 15:29:09 +0000</pubDate>
      <link>https://dev.to/himanshuai/salesforce-testing-with-playwright-typescript-the-complete-practical-guide-2026-edition-da8</link>
      <guid>https://dev.to/himanshuai/salesforce-testing-with-playwright-typescript-the-complete-practical-guide-2026-edition-da8</guid>
      <description>&lt;p&gt;Salesforce is the hardest mainstream web application to automate. The DOM is deeply nested, IDs are auto-generated and change on every render, components hide inside shadow roots, toasts vanish before you can read them, and a single Lightning page can fire a dozen asynchronous XHR calls before it settles. Teams that succeed at Salesforce test automation don't have better selectors — they have a better strategy.&lt;/p&gt;

&lt;p&gt;This is that strategy, written as a practical guide with runnable code. No motivational filler, no "why testing matters" preamble. We go straight into the setup, the authentication approach that eliminates login flakiness, the locator patterns that survive Salesforce re-renders, a Page Object architecture that scales to hundreds of tests, and the handling of every awkward Lightning component you will actually meet: modals, toasts, lookups, comboboxes, datatables, and Visualforce iframes.&lt;/p&gt;

&lt;p&gt;By the end you will have a working framework: reliable login via saved session state, robust locators, page objects, data setup through the API, real end-to-end scenarios, parallel execution, and CI/CD on GitHub Actions and Azure DevOps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want the complete, book-length version?&lt;/strong&gt; This article condenses material covered in full in the &lt;strong&gt;Salesforce Automation Testing Mastery Series (2026 Edition)&lt;/strong&gt; — three books spanning Playwright + TypeScript from zero, enterprise framework design, and complete API testing. Get the bundle: &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/strong&gt;. Questions? Connect on LinkedIn: &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/himanshuai/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/himanshuai/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why Salesforce breaks ordinary automation&lt;/li&gt;
&lt;li&gt;Project setup&lt;/li&gt;
&lt;li&gt;Authentication: log in once, reuse everywhere&lt;/li&gt;
&lt;li&gt;Locator strategy for Lightning&lt;/li&gt;
&lt;li&gt;The Page Object Model, done right&lt;/li&gt;
&lt;li&gt;Handling Lightning components&lt;/li&gt;
&lt;li&gt;Waiting: killing flakiness at the source&lt;/li&gt;
&lt;li&gt;Fast test data with the REST API&lt;/li&gt;
&lt;li&gt;Writing real end-to-end scenarios&lt;/li&gt;
&lt;li&gt;SOQL assertions from the UI layer&lt;/li&gt;
&lt;li&gt;Cross-browser and parallel execution&lt;/li&gt;
&lt;li&gt;Reporting and debugging&lt;/li&gt;
&lt;li&gt;CI/CD integration&lt;/li&gt;
&lt;li&gt;Organizing tests as the suite grows&lt;/li&gt;
&lt;li&gt;Testing across permissions and personas&lt;/li&gt;
&lt;li&gt;More Lightning component patterns&lt;/li&gt;
&lt;li&gt;Diagnosing and eliminating flaky tests&lt;/li&gt;
&lt;li&gt;Visual and accessibility checks&lt;/li&gt;
&lt;li&gt;Best practices checklist&lt;/li&gt;
&lt;li&gt;Where to go next&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Why Salesforce breaks ordinary automation
&lt;/h2&gt;

&lt;p&gt;Before writing a single test, understand what you are fighting. Every flaky Salesforce suite fails for the same handful of reasons, and each has a specific countermeasure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-generated IDs.&lt;/strong&gt; Lightning renders element IDs like &lt;code&gt;input-42&lt;/code&gt; or &lt;code&gt;combobox-button-171&lt;/code&gt;. They change on every page load and every re-render. Any test that keys off these IDs is broken before it starts. The countermeasure is to never use generated IDs — locate by role, label, or visible text instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shadow DOM.&lt;/strong&gt; Lightning Web Components encapsulate their internals in shadow roots. A CSS selector that works in the browser console often returns nothing from a naive automation tool because it cannot pierce the shadow boundary. Playwright's role- and text-based locators pierce open shadow DOM automatically, which is a large part of why it suits Salesforce so well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deep nesting.&lt;/strong&gt; A single field can sit twelve &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; layers deep inside nested components. Brittle XPath chains that encode this structure snap the moment Salesforce ships a UI tweak in its thrice-yearly release. Semantic locators ignore structure entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous rendering.&lt;/strong&gt; Clicking a button often triggers a spinner, an XHR call, a re-render, and a toast — all asynchronous. Tests that assume the page is ready the instant an action completes race against the framework and fail intermittently. Auto-waiting and explicit state assertions solve this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ephemeral toasts.&lt;/strong&gt; Success and error toasts appear and auto-dismiss within a few seconds. A test that navigates away before reading the toast, or reads it after it disappears, produces false negatives. You must assert on the toast in the narrow window it exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session and login friction.&lt;/strong&gt; Logging in through the UI on every test is slow and is itself a common flake source — MFA prompts, "verify your identity" screens, and session timeouts all interfere. The fix is to authenticate once and reuse the session, which we cover in detail below.&lt;/p&gt;

&lt;p&gt;Playwright addresses most of these at the framework level: it auto-waits for elements to be actionable, pierces open shadow DOM, retries assertions until they pass or time out, and lets you persist and reuse authenticated sessions. The rest is discipline in how you write locators and structure tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Project setup
&lt;/h2&gt;

&lt;p&gt;You need Node 18 or newer. Start clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;salesforce-playwright &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;salesforce-playwright
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @playwright/test typescript @types/node dotenv
npx playwright &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;tsconfig.json&lt;/code&gt; with path aliases so imports stay readable as the project grows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CommonJS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolveJsonModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@pages/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/pages/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@fixtures/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/fixtures/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@utils/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/utils/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@data/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/data/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the Playwright config. The important pieces for Salesforce are a generous timeout (Lightning is slow), a &lt;code&gt;baseURL&lt;/code&gt; pointing at your org, &lt;code&gt;storageState&lt;/code&gt; for session reuse, and trace capture on retry so you can debug failures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;fullyParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;junit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results/junit.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_INSTANCE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage/session.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on-first-retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;only-on-failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;retain-on-failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;actionTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;navigationTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Global setup logs in once and saves the session before any test runs.&lt;/span&gt;
  &lt;span class="na"&gt;globalSetup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/global-setup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chromium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msedge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;.env&lt;/code&gt; file (git-ignored) holds the org details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;SF_INSTANCE_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://your-domain.my.salesforce.com&lt;/span&gt;
&lt;span class="py"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://test.salesforce.com&lt;/span&gt;
&lt;span class="py"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;qa.user@yourorg.com.sandbox&lt;/span&gt;
&lt;span class="py"&gt;SF_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;SuperSecret123&lt;/span&gt;
&lt;span class="py"&gt;SF_API_VERSION&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;v62.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;SF_API_VERSION&lt;/code&gt; is deliberately a config value. Salesforce ships three releases a year and each bumps the API version; keeping it in one place means one edit at upgrade time.&lt;/p&gt;

&lt;p&gt;The target folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salesforce-playwright/
├── src/
│   ├── global-setup.ts
│   ├── pages/
│   │   ├── base.page.ts
│   │   ├── login.page.ts
│   │   ├── account.page.ts
│   │   └── opportunity.page.ts
│   ├── fixtures/
│   │   └── pages.fixture.ts
│   ├── utils/
│   │   ├── salesforce-api.ts
│   │   └── lightning.ts
│   └── data/
│       └── factories.ts
├── tests/
├── storage/
└── playwright.config.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Authentication: log in once, reuse everywhere
&lt;/h2&gt;

&lt;p&gt;The single biggest improvement you can make to a Salesforce suite is to stop logging in through the UI on every test. Do it once in global setup, save the browser storage state, and let every test start already authenticated. This removes the slowest and flakiest step from every spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global setup with saved storage state
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/global-setup.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FullConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mkdirSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;globalSetup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FullConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instanceUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_INSTANCE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Log in through the standard Salesforce login page.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Log In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Wait until we land on an authenticated Lightning page.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/lightning|my&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;salesforce&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Persist cookies + storage so tests skip the login flow entirely.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage/session.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;globalSetup&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;playwright.config.ts&lt;/code&gt; sets &lt;code&gt;storageState: 'storage/session.json'&lt;/code&gt;, every test now opens with a live session. Tests navigate straight to the record or list page they need — no login screen, no MFA prompt, no wasted seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  The faster alternative: frontdoor.jsp with an OAuth token
&lt;/h3&gt;

&lt;p&gt;If you already obtain an OAuth access token (for API-driven data setup, covered later), you can skip the UI login entirely using Salesforce's &lt;code&gt;frontdoor.jsp&lt;/code&gt; endpoint, which exchanges a session token for an authenticated browser session and redirects wherever you want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/utils/lightning.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openWithToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;retURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/lightning/page/home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/secur/frontdoor.jsp`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`?sid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;retURL=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retURL&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most robust login method available for Salesforce automation. There is no password typed into a form, no login page to break, and no MFA interruption — you arrive pre-authenticated on the exact page under test. Reserve the UI login in global setup for when you specifically want to test the login experience itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Locator strategy for Lightning
&lt;/h2&gt;

&lt;p&gt;Locators are where Salesforce suites live or die. The rule is simple: locate the way a human describes an element, not the way the DOM happens to nest it. Playwright's semantic locators do exactly this and pierce open shadow DOM as a bonus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefer role, label, and text
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Good — semantic, stable, shadow-DOM-piercing&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Acme Corporation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Search Accounts and more&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your changes were saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Bad — generated IDs and structural XPath that break on every release&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#input-42&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//div[3]/div/div[2]/span/input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The semantic locators read like the interface, so anyone can understand them, and they survive the structural churn that Salesforce introduces with each seasonal release.&lt;/p&gt;

&lt;h3&gt;
  
  
  When you must use CSS, anchor on stable attributes
&lt;/h3&gt;

&lt;p&gt;Some Lightning elements have no accessible name. When you fall back to CSS, anchor on attributes Salesforce keeps stable across renders — &lt;code&gt;data-*&lt;/code&gt; hooks, &lt;code&gt;field-name&lt;/code&gt;, and title attributes — never on generated IDs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Salesforce keeps field-name stable on record form fields.&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[field-name="Industry"] input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Datatable cells expose the API name via data-label / data-col-key.&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-label="Account Name"] a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Toasts carry a stable data-key.&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-notify_toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scope locators to a container
&lt;/h3&gt;

&lt;p&gt;Salesforce pages often show the same label in multiple places — a field in the highlights panel and again in the details tab. Scope your locator to the relevant region so it resolves to exactly one element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;detailPanel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabpanel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;detailPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Phone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+1 415 555 0100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build a small locator vocabulary
&lt;/h3&gt;

&lt;p&gt;Rather than repeat raw locators everywhere, centralize the recurring Lightning patterns in a helper. This keeps every page object consistent and gives you one place to adjust if Salesforce changes a class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/utils/lightning.ts (continued)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lightning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-notify_toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-spinner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-modal__container, section[role="dialog"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;recordField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[field-name="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="na"&gt;actionButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. The Page Object Model, done right
&lt;/h2&gt;

&lt;p&gt;The Page Object Model keeps locators and page interactions out of your test bodies. Tests describe intent; page objects know how to fulfill it. Done well, a Salesforce UI change touches one page object, not fifty tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  A base page for shared behavior
&lt;/h3&gt;

&lt;p&gt;Start with a base class that every page object extends. It owns the &lt;code&gt;Page&lt;/code&gt; handle and the cross-cutting behavior that every Salesforce page shares — waiting for the app to settle, reading toasts, and navigating to records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/base.page.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lightning&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@utils/lightning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="cm"&gt;/** Wait until Lightning has finished its spinners and network activity. */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;waitUntilReady&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Spinners come and go; wait for none to be visible.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spinner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lightning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/** Read and assert the success toast within its brief lifetime. */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;expectSuccessToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lightning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/** Navigate directly to a record page by object and Id. */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;gotoRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;objectApiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/r/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;objectApiName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;recordId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/view`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntilReady&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/** Navigate to an object's list view. */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;gotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;objectApiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/o/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;objectApiName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/list`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntilReady&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A concrete page object
&lt;/h3&gt;

&lt;p&gt;Now a page object for the Account record page. Locators are declared once as class properties; methods express the operations a test cares about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/account.page.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BasePage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./base.page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;newButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;saveButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Locator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nameInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;openNewForm&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fillName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nameInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;selectIndustry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Lightning picklists are comboboxes — open then choose.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Industry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expectSuccessToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/was &lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;created|saved&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getFieldValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fieldLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;records-record-layout-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hasText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fieldLabel&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lightning-formatted-text, lightning-formatted-number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;())?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test reads as a story, with no locator noise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccountPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openNewForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Playwright Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectIndustry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inject page objects with fixtures
&lt;/h3&gt;

&lt;p&gt;Instantiating page objects by hand in every test is repetitive. Playwright fixtures inject ready-made page objects into any test that declares them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/fixtures/pages.fixture.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AccountPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@pages/account.page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OpportunityPage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@pages/opportunity.page&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;accountPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AccountPage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;opportunityPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OpportunityPage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Pages&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;accountPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccountPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;opportunityPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpportunityPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests import from the fixture and receive fully-built page objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/pages.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creates an Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;accountPage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accountPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openNewForm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accountPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fixture Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accountPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectIndustry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;accountPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Handling Lightning components
&lt;/h2&gt;

&lt;p&gt;Standard automation patterns fall short on Salesforce's custom components. Each of the tricky ones has a reliable pattern. Learn these once and you handle 95% of what Lightning throws at you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comboboxes and picklists
&lt;/h3&gt;

&lt;p&gt;Lightning picklists are not native &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; elements; they are custom comboboxes. You open them with a click and choose an option by its role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;selectComboboxOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lookup fields
&lt;/h3&gt;

&lt;p&gt;Lookups are search-as-you-type comboboxes that query related records. Type part of the name, wait for the dropdown, then pick the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;selectLookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fieldLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combobox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;combobox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fieldLabel&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;combobox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;combobox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Wait for the async search results, then click the matching option.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;i&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is patience: the dropdown is populated by an XHR call, so the option does not exist the instant you finish typing. Locating by role and letting Playwright auto-wait handles the timing without a hardcoded sleep.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modals and dialogs
&lt;/h3&gt;

&lt;p&gt;Actions like New, Edit, and Change Owner open modals. Scope your interactions to the modal container so you don't accidentally match a same-named element on the page behind it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;actInModal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// Modal should close after a successful save.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeHidden&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Toast messages
&lt;/h3&gt;

&lt;p&gt;Toasts are the most timing-sensitive element in Salesforce. They appear after an action and auto-dismiss within a few seconds. Assert on them immediately after the triggering action, and never insert navigation between the action and the assertion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;expectToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-notify_toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toContainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your test navigates away before checking the toast, you will get intermittent failures that are maddening to diagnose. The discipline is: act, assert the toast, then move on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Datatables and list views
&lt;/h3&gt;

&lt;p&gt;List views and related lists render as datatables. Locate rows by their content and cells by column, using the stable &lt;code&gt;data-label&lt;/code&gt; attribute Salesforce puts on each cell.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;clickRowLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCellText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;columnLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[data-label="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;columnLabel&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;())?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Visualforce iframes
&lt;/h3&gt;

&lt;p&gt;Some legacy pages embed Visualforce inside an iframe. Playwright's &lt;code&gt;frameLocator&lt;/code&gt; steps into the frame so you can interact with its contents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fillVisualforceField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frameLocator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iframe[title*="content"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tabs and record detail sections
&lt;/h3&gt;

&lt;p&gt;Record pages organize fields into tabs. Activate the tab before asserting on fields inside it, since inactive tab panels may not render their contents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tabName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tabName&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tabpanel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tabName&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Waiting: killing flakiness at the source
&lt;/h2&gt;

&lt;p&gt;Flakiness in Salesforce almost always traces back to a test asserting on state before the framework has produced it. The cure is never a fixed &lt;code&gt;waitForTimeout&lt;/code&gt; — that either wastes time or fails under load. The cure is asserting on the specific condition you are actually waiting for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assert on state, not on time
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fragile — guesses how long rendering takes.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Acme Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Robust — waits exactly as long as needed, no more.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Acme Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright's web-first assertions retry automatically until they pass or the timeout expires. Give them a generous timeout for Lightning and they absorb the platform's variable rendering speed without a single hardcoded pause.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait for spinners to clear
&lt;/h3&gt;

&lt;p&gt;Lightning shows a spinner during saves and navigations. Wait for it to disappear before interacting further.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;waitForSpinnersGone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-spinner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toHaveCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wait for network to settle after navigation
&lt;/h3&gt;

&lt;p&gt;After navigating to a record, wait for &lt;code&gt;networkidle&lt;/code&gt; so all the component XHR calls that populate the page have completed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/r/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/view`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retry only what should be retried
&lt;/h3&gt;

&lt;p&gt;Configure retries in the config so genuinely transient failures get a second chance, but never wrap assertions in manual retry loops that hide real bugs. A test that only passes on retry is telling you something — investigate patterns of retried tests rather than accepting them silently.&lt;/p&gt;

&lt;p&gt;The mindset shift is this: every wait should name the condition it is waiting for. If you cannot name the condition, you are guessing, and guesses are what make suites flaky.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Fast test data with the REST API
&lt;/h2&gt;

&lt;p&gt;Building test data through the UI is slow and pointless. The API creates a fully-related data graph in one call, and your UI test starts with exactly the state it needs. This is the biggest speed and stability win available after session reuse.&lt;/p&gt;

&lt;h3&gt;
  
  
  A minimal API helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/utils/salesforce-api.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesforceApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_API_VERSION&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v62.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth2/token`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_SECURITY_TOKEN&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Login failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;extraHTTPHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Create &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;soql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/query`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;soql&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data factories
&lt;/h3&gt;

&lt;p&gt;Wrap record creation in factories that provide sensible defaults, so tests specify only what matters to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/data/factories.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@utils/salesforce-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Test Account &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createOpportunity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Test Opp &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prospecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-12-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wiring API setup into a UI test
&lt;/h3&gt;

&lt;p&gt;Combine the API for setup and the browser for verification. The API builds the Account and Opportunity instantly; the browser only checks what a user would see.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/pages.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@utils/salesforce-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createOpportunity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@data/factories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity appears on the Account related list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Related List Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createOpportunity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Visible Deal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Jump straight to the record — session is already authenticated.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/r/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/view`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Related/&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Visible Deal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Cleanup — remove children before parents.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`SELECT Id FROM Opportunity WHERE AccountId = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;opps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting up through the API turned what would be a two-minute click-fest into a sub-second operation, and the browser now tests only the behavior that genuinely needs a browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Writing real end-to-end scenarios
&lt;/h2&gt;

&lt;p&gt;With the building blocks in place, real business flows become readable, reliable tests. Consider a sales flow: create an Account, add an Opportunity, move it through stages, and confirm it closes. This is the kind of scenario that matters to the business and that regression suites should guard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/e2e/sales-cycle.ui.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/pages.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@utils/salesforce-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createAccount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@data/factories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sales cycle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Cycle Corp &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`SELECT Id FROM Opportunity WHERE AccountId = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;opps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;takes an Opportunity from Prospecting to Closed Won&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/r/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/view`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a new Opportunity from the Account.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tab&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Related/&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dialog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enterprise Deal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Close Date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-12-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prospecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;modal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-notify_toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toContainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/created/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Advance the stage via the path component to Closed Won.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enterprise Deal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/Closed Won/&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mark as Complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-notify_toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toContainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/success|updated/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Verify the outcome through the data layer for certainty.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;opp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`SELECT StageName FROM Opportunity WHERE AccountId = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' LIMIT 1`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Closed Won&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the pattern: the UI performs the actions a user performs, and the final assertion confirms the outcome through the API. This double-check — behavior through the browser, result through the data layer — is far stronger than a UI-only assertion, because it verifies that the action actually persisted rather than just that a toast appeared.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. SOQL assertions from the UI layer
&lt;/h2&gt;

&lt;p&gt;UI assertions confirm what a user sees; SOQL assertions confirm what was actually written. The most reliable Salesforce tests use both. After a UI action, query the record and assert on its true state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account industry persists after edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Edit Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Retail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/r/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/view`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Edit Industry inline.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Edit Industry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;combobox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Industry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-notify_toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toContainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/saved/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Assert on the persisted value, not just the on-screen text.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`SELECT Industry FROM Account WHERE Id = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern catches a whole class of bugs the UI alone misses: cases where the screen shows a value but the save silently failed, or where a validation rule quietly reverted the change. Querying the source of truth removes all ambiguity.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Cross-browser and parallel execution
&lt;/h2&gt;

&lt;p&gt;Salesforce officially supports Chrome, Edge, Firefox, and Safari. Playwright runs all of them from one config, and its parallel model lets a large suite finish in a fraction of the wall-clock time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple browser projects
&lt;/h3&gt;

&lt;p&gt;The config already defines Chromium and Edge projects. Add Firefox and WebKit to cover the full matrix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chromium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;msedge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Firefox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;devices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Desktop Safari&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run a single browser during development and the full matrix in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;chromium          &lt;span class="c"&gt;# fast local loop&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt;                              &lt;span class="c"&gt;# all browsers in CI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parallelism and data isolation
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;fullyParallel: true&lt;/code&gt; and multiple workers, tests run simultaneously. The one rule that makes parallelism safe on Salesforce is data isolation: every test must create its own records and never depend on data another test might be mutating. The factories from earlier, combined with per-test cleanup, give you exactly that. Unique names (using a timestamp or random suffix) prevent collisions when many workers create similar records at once.&lt;/p&gt;

&lt;p&gt;Because each test owns its data and cleans up after itself, you can scale workers up freely. The limiting factor becomes your org's API and login limits, not test interference — which is another reason to reuse the session and set up data through batched API calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Reporting and debugging
&lt;/h2&gt;

&lt;p&gt;When a Salesforce test fails at 2 a.m. in CI, the report is all you have. Configure rich artifacts so failures are diagnosable without re-running anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trace viewer
&lt;/h3&gt;

&lt;p&gt;The config sets &lt;code&gt;trace: 'on-first-retry'&lt;/code&gt;. On a failed retry, Playwright records a full trace — DOM snapshots, network calls, console logs, and a frame-by-frame timeline. Open it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright show-trace trace.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trace viewer is the single most valuable debugging tool for Salesforce, because it lets you scrub through exactly what the page looked like at each step and see which XHR call was still pending when an assertion failed. Most "impossible" Lightning flakes become obvious in the trace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots and video
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;screenshot: 'only-on-failure'&lt;/code&gt; and &lt;code&gt;video: 'retain-on-failure'&lt;/code&gt; capture visual evidence of every failure with no cost on passing runs. Combined with the trace, you rarely need to reproduce a failure locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  The HTML report
&lt;/h3&gt;

&lt;p&gt;The HTML reporter produces a browsable report with each test's steps, timings, and attached artifacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright show-report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Targeted debugging during development
&lt;/h3&gt;

&lt;p&gt;For local debugging, run a single test headed with the inspector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright &lt;span class="nb"&gt;test &lt;/span&gt;sales-cycle &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;chromium &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens the Playwright Inspector, which steps through the test and highlights each locator on the page — invaluable when a Lightning locator is not matching what you expect.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. CI/CD integration
&lt;/h2&gt;

&lt;p&gt;Tests earn their keep only when they run automatically on every change. Both major platforms integrate cleanly. The shared concerns are: install dependencies and browsers, supply credentials as secrets, run the suite, and publish artifacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/salesforce-tests.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Salesforce UI Tests&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright install --with-deps chromium&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;SF_INSTANCE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_INSTANCE_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://test.salesforce.com&lt;/span&gt;
          &lt;span class="na"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_PASSWORD }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_CLIENT_SECRET }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_API_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v62.0&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright test --project=chromium&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload report&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report/&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;if: always()&lt;/code&gt; on the upload step ensures you get the report even when tests fail — which is precisely when you need it. All credentials come from repository secrets, never the repo itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure DevOps
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# azure-pipelines.yml&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodeTool@0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;versionSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20.x'&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright install --with-deps chromium&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install browsers&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright test --project=chromium&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;SF_INSTANCE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(SF_INSTANCE_URL)&lt;/span&gt;
      &lt;span class="na"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://test.salesforce.com&lt;/span&gt;
      &lt;span class="na"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(SF_USERNAME)&lt;/span&gt;
      &lt;span class="na"&gt;SF_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(SF_PASSWORD)&lt;/span&gt;
      &lt;span class="na"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(SF_CLIENT_ID)&lt;/span&gt;
      &lt;span class="na"&gt;SF_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(SF_CLIENT_SECRET)&lt;/span&gt;
      &lt;span class="na"&gt;SF_API_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v62.0&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishTestResults@2&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;testResultsFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JUnit'&lt;/span&gt;
      &lt;span class="na"&gt;testResultsFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;results/junit.xml'&lt;/span&gt;
      &lt;span class="na"&gt;testRunTitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Salesforce&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;UI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tests'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JUnit reporter configured earlier feeds Azure's native test-results tab, giving pass/fail trends over time. Store credentials as secret pipeline variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline strategy
&lt;/h3&gt;

&lt;p&gt;Run a fast single-browser pass on every pull request as a merge gate, and reserve the full cross-browser matrix for merges to main or a nightly schedule. This keeps developer feedback tight while still exercising the complete matrix regularly. Because browsers are separate Playwright projects, splitting stages is a matter of which &lt;code&gt;--project&lt;/code&gt; flags you pass.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Organizing tests as the suite grows
&lt;/h2&gt;

&lt;p&gt;A dozen tests are easy. Three hundred are not, unless you impose structure from the start. Salesforce suites grow fast because every object, every flow, and every permission set is a candidate for coverage. Organization is what keeps that growth maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Group by feature, tag by purpose
&lt;/h3&gt;

&lt;p&gt;Structure test files by business feature — accounts, opportunities, cases, quotes — not by technical layer. Within them, use tags to slice the suite for different pipeline stages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/pages.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity management&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creates an opportunity @smoke @regression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;opportunityPage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recalculates amount from line items @regression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;opportunityPage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run just the smoke set as a fast merge gate, and the full regression set nightly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; @smoke        &lt;span class="c"&gt;# fast gate on every PR&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; @regression   &lt;span class="c"&gt;# full suite nightly&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tags let one suite serve multiple purposes without duplicating tests. A &lt;code&gt;@smoke&lt;/code&gt; subset gives developers feedback in a couple of minutes, while &lt;code&gt;@regression&lt;/code&gt; runs the exhaustive matrix when time is less critical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Share setup with describe-level hooks
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;beforeAll&lt;/code&gt; for expensive one-time setup like API login, and &lt;code&gt;beforeEach&lt;/code&gt; for per-test data. Keep hooks small and push real logic into helpers and factories so the intent of each test stays visible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Case escalation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalesforceApi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;testInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Attach the run context to the report for easier debugging.&lt;/span&gt;
    &lt;span class="nx"&gt;testInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;org&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_INSTANCE_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keep tests independent
&lt;/h3&gt;

&lt;p&gt;Every test must pass in isolation and in any order. A test that depends on a record another test created is a latent failure waiting for the day parallelism or a &lt;code&gt;--grep&lt;/code&gt; filter changes the execution order. The factories and per-test cleanup shown earlier enforce this independence, and it is worth treating as non-negotiable: no test reads or mutates data owned by another.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Testing across permissions and personas
&lt;/h2&gt;

&lt;p&gt;Salesforce behavior is profile- and permission-driven. The same page shows different fields, buttons, and record access depending on who is logged in. A serious suite tests more than one persona — at minimum a standard user and an admin, and often specific personas like a sales rep, a support agent, and a read-only auditor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple saved sessions
&lt;/h3&gt;

&lt;p&gt;Extend global setup to log in as each persona and save a separate storage state per role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/global-setup.ts (multi-persona)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mkdirSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;personas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_ADMIN_USER&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_ADMIN_PASS&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sales&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_SALES_USER&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_SALES_PASS&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loginAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Log In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/lightning|my&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;salesforce&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;globalSetup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;mkdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;personas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loginAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`storage/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Select a persona per test
&lt;/h3&gt;

&lt;p&gt;Override &lt;code&gt;storageState&lt;/code&gt; at the test or describe level to run as a specific role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Admin-only actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage/admin.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin can change record owner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sales rep view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage/sales.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sales rep cannot see the Delete button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/o/Account/list`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toHaveCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing negative cases — that a persona &lt;em&gt;cannot&lt;/em&gt; do something — is as important as testing that an admin can. Permission regressions are among the most dangerous Salesforce bugs because they silently expose or hide data, and a UI-only manual test rarely catches them across every profile.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. More Lightning component patterns
&lt;/h2&gt;

&lt;p&gt;Beyond the core components covered earlier, a handful of specialized inputs come up often enough to keep patterns ready for them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Date pickers
&lt;/h3&gt;

&lt;p&gt;Lightning date fields accept typed input directly, which is more reliable than clicking through the calendar grid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// value formatted per the org's locale, e.g. '12/31/2026'.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;combobox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Escape&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// close the calendar overlay&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-select dual list boxes
&lt;/h3&gt;

&lt;p&gt;Some fields render as dual list boxes where you move options from available to selected. Locate options by text and use the move buttons.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;selectFromDualListbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;option&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;option&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;option&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Move selection to Chosen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  File uploads
&lt;/h3&gt;

&lt;p&gt;Lightning file uploads use a hidden native file input. Set the file directly on it rather than trying to drive the drag-and-drop overlay.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input[type="file"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setInputFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Wait for the upload to register in the files related list.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/1 of 1 file uploaded/i&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rich text editors
&lt;/h3&gt;

&lt;p&gt;Rich text fields render inside a contenteditable region. Target it and type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fillRichText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lightning-input-rich-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;hasText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[contenteditable="true"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Global search
&lt;/h3&gt;

&lt;p&gt;The global search box drives navigation across the whole org. Type, submit, and select from results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;globalSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByPlaceholder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Search.*and more/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these follows the same philosophy as the core patterns: locate by role or a stable attribute, let Playwright auto-wait for the element to be actionable, and assert on a resulting state rather than sleeping. Once the patterns are in your utilities, they are one function call away in any test.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Diagnosing and eliminating flaky tests
&lt;/h2&gt;

&lt;p&gt;A flaky test — one that passes and fails without any code change — is worse than a failing test, because it erodes trust in the whole suite. On Salesforce, flakiness is common but almost always traceable to a small set of causes. Here is how to hunt each one down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Find the pattern first
&lt;/h3&gt;

&lt;p&gt;Before touching a flaky test, gather data. Configure retries and let CI record which tests are retried; a test that consistently needs a retry is telling you something specific. Playwright's HTML report flags flaky tests distinctly — ones that failed then passed on retry. Treat that list as a backlog, not as background noise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Run a suspect test many times to reproduce intermittent failure.&lt;/span&gt;
&lt;span class="c1"&gt;// npx playwright test suspect.spec --repeat-each=20 --project=chromium&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reproducing locally with &lt;code&gt;--repeat-each&lt;/code&gt; is the fastest way to confirm a fix actually works rather than getting lucky once.&lt;/p&gt;

&lt;h3&gt;
  
  
  The usual causes and their fixes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Racing the render.&lt;/strong&gt; The test asserts before Lightning finishes rendering. Fix: replace any &lt;code&gt;waitForTimeout&lt;/code&gt; with a web-first assertion on the specific element or state, and wait for spinners to clear after actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Toast timing.&lt;/strong&gt; The test reads a toast after it auto-dismissed, or navigates before reading it. Fix: assert on the toast immediately after the triggering action, with no intervening navigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale locators after re-render.&lt;/strong&gt; Lightning re-renders a component and the previously resolved element detaches. Fix: let Playwright re-resolve the locator at action time — never store a resolved element handle across an action that triggers a re-render.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared or leftover data.&lt;/strong&gt; A prior run left a record that collides with this run, or two parallel tests touch the same data. Fix: unique names per record and strict per-test cleanup, so no test depends on external state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session expiry.&lt;/strong&gt; A long suite outlives the saved session. Fix: keep the integration user's session timeout generous, and re-run global setup if a run is expected to exceed it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the fix, then prove it
&lt;/h3&gt;

&lt;p&gt;After a fix, run the test twenty times with &lt;code&gt;--repeat-each&lt;/code&gt;. If it passes every time, the fix holds. If it fails even once, the root cause is still there. This discipline — reproduce, fix, prove — is what separates a suite teams trust from one they learn to ignore. A flaky test that is quarantined and forgotten is a coverage gap in disguise; hunt each one to root cause rather than adding blanket retries that mask real product bugs.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Visual and accessibility checks
&lt;/h2&gt;

&lt;p&gt;Functional tests confirm behavior, but two other dimensions are cheap to add and catch bugs functional tests miss: visual regressions and accessibility violations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual regression snapshots
&lt;/h3&gt;

&lt;p&gt;Playwright can capture a screenshot and compare it against a stored baseline, failing if the pixels drift beyond a threshold. This catches layout breaks and unintended styling changes that no functional assertion would notice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account page layout is visually stable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/o/Account/home`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Mask dynamic regions so timestamps and record counts don't cause false diffs.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveScreenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;account-home.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxDiffPixelRatio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.slds-notify_toast&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-label="Last Modified"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mask&lt;/code&gt; option is essential on Salesforce, where timestamps, record counts, and toasts change between runs. Mask them so the comparison focuses on the stable structure of the page rather than volatile data. Update baselines deliberately with &lt;code&gt;--update-snapshots&lt;/code&gt; when a change is intentional.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessibility assertions
&lt;/h3&gt;

&lt;p&gt;Salesforce ships accessible components, but customizations and custom Lightning Web Components can introduce violations. Integrate an accessibility scanner to catch these in CI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @axe-core/playwright
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AxeBuilder&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@axe-core/playwright&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account creation form has no critical a11y violations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/lightning/o/Account/new`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForLoadState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AxeBuilder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTags&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wcag2a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wcag2aa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;critical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;impact&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scoping to critical violations keeps the check actionable rather than drowning you in minor warnings on standard Salesforce markup. As custom components enter your org, this scan becomes a genuine safety net for a class of defects that functional and visual tests both miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Best practices checklist
&lt;/h2&gt;

&lt;p&gt;Everything above distills to a set of principles worth keeping in front of you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reuse the session.&lt;/strong&gt; Log in once in global setup and save storage state, or use &lt;code&gt;frontdoor.jsp&lt;/code&gt; with an OAuth token. Never log in through the UI in every test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Locate semantically.&lt;/strong&gt; Prefer role, label, and text locators. They pierce shadow DOM and survive Salesforce's seasonal UI changes. Never use auto-generated IDs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope your locators.&lt;/strong&gt; Salesforce repeats labels across regions. Anchor locators to a tab panel, modal, or related-list container so they resolve to exactly one element.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wait on conditions, not on time.&lt;/strong&gt; Every wait should name the state it awaits. Replace &lt;code&gt;waitForTimeout&lt;/code&gt; with web-first assertions that retry until the condition holds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set up data by API.&lt;/strong&gt; Build test data with batched API calls and start the browser on the exact page under test. This is faster and far more stable than clicking data into existence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assert on both layers.&lt;/strong&gt; Confirm behavior through the UI and the persisted result through SOQL. This catches silent save failures the UI alone would miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isolate test data.&lt;/strong&gt; Every test creates and cleans up its own records with unique names, so parallel workers never collide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read toasts immediately.&lt;/strong&gt; Assert on toasts in the narrow window they exist, before any navigation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capture traces on failure.&lt;/strong&gt; The trace viewer turns unreproducible Lightning flakes into obvious, diagnosable failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pin the API version in config.&lt;/strong&gt; One place to change when a new Salesforce release lands, and no surprises from default-behavior shifts.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. Where to go next
&lt;/h2&gt;

&lt;p&gt;You now have a complete, code-backed foundation for Salesforce UI and end-to-end testing: session reuse that eliminates login flakiness, semantic locators that survive re-renders, a scalable Page Object architecture, reliable handling of every awkward Lightning component, condition-based waiting, API-driven data setup, dual-layer assertions, cross-browser parallel execution, rich reporting, and CI/CD on both major platforms.&lt;/p&gt;

&lt;p&gt;The natural next steps are deepening each area: building a richer data-factory layer with related-record graphs, adding visual regression checks on key pages, layering in accessibility assertions, parameterizing tests across sandboxes, integrating results into release gates so a failing regression blocks a deploy, and extending coverage into the API layer to test the integration points directly.&lt;/p&gt;

&lt;p&gt;If you want the full, structured path — from your first Playwright test through enterprise framework design and complete API testing — that is exactly what the series below was written to deliver.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get the complete series
&lt;/h2&gt;

&lt;p&gt;This article covers the essentials of Salesforce UI testing. The full material goes much deeper: complete framework architectures, hundreds of runnable examples, advanced page-object and fixture patterns, parallel-execution tuning, reporting pipelines, and production-ready CI/CD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Salesforce Automation Testing Mastery Series (2026 Edition)&lt;/strong&gt; — three books, one bundle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Book 1 — Salesforce Testing with Playwright + TypeScript:&lt;/strong&gt; from zero to Automation Engineer. Lightning UI, Page Object design, SOQL basics, and end-to-end scenarios — the full depth behind this article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Book 2 — Enterprise Salesforce Framework Design:&lt;/strong&gt; scalable framework architecture, fixtures and utilities, parallel execution, and CI/CD integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Book 3 — Salesforce API Testing with Playwright + TypeScript:&lt;/strong&gt; OAuth, REST, Composite, Bulk, and Tooling APIs, plus API + UI validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Get the bundle:&lt;/strong&gt; &lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have questions or want to talk shop?&lt;/strong&gt; Connect with me on LinkedIn: &lt;a href="https://www.linkedin.com/in/himanshuai/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/himanshuai/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build. Automate. Scale. Deliver — like an enterprise.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>salesforce</category>
      <category>playwright</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Don't miss your chance to get 90% OFF on my premium QA, AI Testing, MCP, RAG, LLM, Salesforce, Playwright, and AWS bundles.</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Wed, 29 Jul 2026 18:35:51 +0000</pubDate>
      <link>https://dev.to/himanshuai/dont-miss-your-chance-to-get-90-off-on-my-premium-qa-ai-testing-mcp-rag-llm-salesforce-23ch</link>
      <guid>https://dev.to/himanshuai/dont-miss-your-chance-to-get-90-off-on-my-premium-qa-ai-testing-mcp-rag-llm-salesforce-23ch</guid>
      <description>&lt;p&gt;Hi,&lt;/p&gt;

&lt;p&gt;Don't miss your chance to get 90% OFF on my premium QA, AI Testing, MCP, RAG, LLM, Salesforce, Playwright, and AWS bundles.&lt;/p&gt;

&lt;p&gt;Use coupon code: JUPITER90&lt;/p&gt;

&lt;p&gt;Browse all bundles:&lt;br&gt;
&lt;a href="https://himanshuai.gumroad.com" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⏳ Offer ends soon. Grab it now before the discount expires!&lt;/p&gt;

&lt;p&gt;Happy Learning,&lt;br&gt;
Himanshu Agarwal&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Salesforce API Testing with Playwright + TypeScript: A Practical Enterprise Guide (2026 Edition)</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Wed, 29 Jul 2026 09:37:14 +0000</pubDate>
      <link>https://dev.to/himanshuai/salesforce-api-testing-with-playwright-typescript-a-practical-enterprise-guide-2026-edition-36d2</link>
      <guid>https://dev.to/himanshuai/salesforce-api-testing-with-playwright-typescript-a-practical-enterprise-guide-2026-edition-36d2</guid>
      <description>&lt;p&gt;Modern Salesforce implementations are API-first. Lightning Web Components call Apex REST endpoints, integration middleware pushes records through the Bulk API, external systems authenticate with OAuth, and CI/CD pipelines deploy metadata through the Tooling API. If your automation strategy only clicks buttons in the UI, you are testing the thin surface of a system whose real logic lives underneath.&lt;/p&gt;

&lt;p&gt;This article is a hands-on guide to testing Salesforce APIs with Playwright and TypeScript. No motivational filler — just the authentication flows, the request patterns, the framework structure, and the pipeline configuration you need to build production-grade API tests. Every section includes runnable code you can drop into a project and adapt.&lt;/p&gt;

&lt;p&gt;By the end you will be able to authenticate with OAuth (username-password, JWT bearer, and client credentials), exercise the REST, Composite, and Bulk APIs, drive the Tooling API for metadata and anonymous Apex, combine API setup with UI verification, and wire the whole thing into GitHub Actions and Azure DevOps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want the complete, book-length version?&lt;/strong&gt; This article is a condensed walkthrough of material covered in depth in the &lt;strong&gt;Salesforce Automation Testing Mastery Series (2026 Edition)&lt;/strong&gt; — three books covering Playwright + TypeScript testing, enterprise framework design, and API testing end to end. Grab the bundle here: &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;/strong&gt; (use code &lt;code&gt;JUPITER80&lt;/code&gt; for the launch discount). Questions? Connect with me on LinkedIn: &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/himanshuai/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/himanshuai/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Why test Salesforce at the API layer&lt;/li&gt;
&lt;li&gt;Project setup: Playwright + TypeScript&lt;/li&gt;
&lt;li&gt;Configuration and secrets&lt;/li&gt;
&lt;li&gt;OAuth authentication and Connected Apps&lt;/li&gt;
&lt;li&gt;The Salesforce REST API: CRUD, SOQL, and upsert&lt;/li&gt;
&lt;li&gt;Composite APIs: batching, dependent requests, and trees&lt;/li&gt;
&lt;li&gt;Bulk API 2.0: ingest and query jobs&lt;/li&gt;
&lt;li&gt;The Tooling API: metadata queries and anonymous Apex&lt;/li&gt;
&lt;li&gt;Building a reusable API client&lt;/li&gt;
&lt;li&gt;Fixtures: injecting authenticated clients into tests&lt;/li&gt;
&lt;li&gt;Combining API setup with UI validation&lt;/li&gt;
&lt;li&gt;Contract testing against the Salesforce schema&lt;/li&gt;
&lt;li&gt;Error handling, retries, and rate limits&lt;/li&gt;
&lt;li&gt;Test data lifecycle and cleanup&lt;/li&gt;
&lt;li&gt;CI/CD: GitHub Actions and Azure DevOps&lt;/li&gt;
&lt;li&gt;Best practices checklist&lt;/li&gt;
&lt;li&gt;Where to go next&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Why test Salesforce at the API layer
&lt;/h2&gt;

&lt;p&gt;UI tests are slow, brittle, and expensive to maintain. A single Lightning page can take several seconds to render, and any layout change can break a selector. API tests, by contrast, hit the platform directly. They run in milliseconds, they are deterministic, and they verify the exact contract that integrations depend on.&lt;/p&gt;

&lt;p&gt;There are four concrete reasons to invest in API testing on Salesforce:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed.&lt;/strong&gt; An API round trip to create an Account and assert on the response is an order of magnitude faster than navigating the UI to do the same. When you have thousands of assertions, that difference decides whether your suite runs in minutes or hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data setup.&lt;/strong&gt; Even for UI tests, you rarely want to build test data through the UI. Creating a fully-related Account → Contact → Opportunity graph by clicking is wasteful. The API sets up state in one call, then the UI test focuses only on the behavior under test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration coverage.&lt;/strong&gt; External systems talk to Salesforce over REST, Bulk, and SOAP. Those integration points are where production incidents happen — a field renamed, a validation rule added, a picklist value removed. API tests catch these breaks before your partners do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend logic.&lt;/strong&gt; Apex triggers, flows, validation rules, and roll-up summaries all fire on API writes. Testing through the API exercises this logic directly, without the UI as an intermediary that can mask or alter behavior.&lt;/p&gt;

&lt;p&gt;Playwright is an excellent fit here. Although it is best known as a browser automation tool, its &lt;code&gt;APIRequestContext&lt;/code&gt; is a first-class HTTP client with cookie handling, automatic retries, request tracing, and tight integration with the Playwright test runner. You get one framework, one config, and one report for both API and UI tests.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Project setup: Playwright + TypeScript
&lt;/h2&gt;

&lt;p&gt;Start with a clean Node project. You need Node 18 or newer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;salesforce-api-tests &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;salesforce-api-tests
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @playwright/test typescript @types/node dotenv
npx playwright &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;tsconfig.json&lt;/code&gt; tuned for a test project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CommonJS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolveJsonModule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"baseUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@clients/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/clients/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@config/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/config/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@fixtures/*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/fixtures/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the Playwright config. Note that we do not set a global &lt;code&gt;baseURL&lt;/code&gt; on the top-level &lt;code&gt;use&lt;/code&gt; block, because the Salesforce instance URL is only known after authentication — different orgs and sandboxes resolve to different &lt;code&gt;*.my.salesforce.com&lt;/code&gt; hosts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;testDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;fullyParallel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;never&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;junit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results/junit.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on-first-retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ignoreHTTPSErrors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/.*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;api&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;spec&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;ts/&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/.*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;ui&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;spec&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;ts/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Splitting into &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;ui&lt;/code&gt; projects lets you run &lt;code&gt;npx playwright test --project=api&lt;/code&gt; in a fast pipeline stage and reserve browser runs for the slower stage.&lt;/p&gt;

&lt;p&gt;The folder layout we will build toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;salesforce-api-tests/
├── src/
│   ├── config/
│   │   └── environment.ts
│   ├── auth/
│   │   ├── oauth.ts
│   │   └── jwt.ts
│   ├── clients/
│   │   ├── salesforce-client.ts
│   │   ├── rest-client.ts
│   │   ├── bulk-client.ts
│   │   └── tooling-client.ts
│   └── fixtures/
│       └── salesforce.fixture.ts
├── tests/
│   ├── rest/
│   ├── composite/
│   ├── bulk/
│   └── ui/
├── playwright.config.ts
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Configuration and secrets
&lt;/h2&gt;

&lt;p&gt;Never hardcode credentials. Read everything from environment variables, validate them at startup, and fail fast with a clear message if something is missing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/config/environment.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Missing required environment variable: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SalesforceEnv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;securityToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Use https://test.salesforce.com for sandboxes.&lt;/span&gt;
  &lt;span class="na"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_LOGIN_URL&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://login.salesforce.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_API_VERSION&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v62.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SF_PASSWORD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;securityToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_SECURITY_TOKEN&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A local &lt;code&gt;.env&lt;/code&gt; file (git-ignored) for development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://test.salesforce.com&lt;/span&gt;
&lt;span class="py"&gt;SF_API_VERSION&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;v62.0&lt;/span&gt;
&lt;span class="py"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;3MVG9...&lt;/span&gt;
&lt;span class="py"&gt;SF_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1A2B3C...&lt;/span&gt;
&lt;span class="py"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;integration.user@yourorg.com.sandbox&lt;/span&gt;
&lt;span class="py"&gt;SF_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;SuperSecret123&lt;/span&gt;
&lt;span class="py"&gt;SF_SECURITY_TOKEN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;abcXYZ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;apiVersion&lt;/code&gt; is a config value on purpose. Salesforce ships three releases a year, and each bumps the API version. Keeping it in one place means a single change when you upgrade. Pick a version your org supports; recent sandboxes accept the latest, but pinning avoids surprises when a new release changes default behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. OAuth authentication and Connected Apps
&lt;/h2&gt;

&lt;p&gt;Every API call needs a bearer token, and every token comes from an OAuth flow against a Connected App. You configure a Connected App once in Setup (App Manager → New Connected App), enable OAuth, and choose the flows you need. The three flows most useful for automated testing are username-password, JWT bearer, and client credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Username-password flow
&lt;/h3&gt;

&lt;p&gt;The simplest flow for a service account. You send the username, the password concatenated with the security token, and the Connected App's consumer key and secret. Salesforce returns an access token and the instance URL you must use for all subsequent calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/auth/oauth.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loginWithPassword&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth2/token`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;securityToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`OAuth password login failed (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issued_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tokenType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The security token is appended directly to the password with no separator. If your integration user's IP is allow-listed in the org's login IP ranges, the security token can be empty — which is why we default &lt;code&gt;SF_SECURITY_TOKEN&lt;/code&gt; to an empty string.&lt;/p&gt;

&lt;h3&gt;
  
  
  JWT bearer flow
&lt;/h3&gt;

&lt;p&gt;For CI pipelines, JWT bearer is the better choice. There is no password to store or rotate; you sign a short-lived assertion with a private key whose certificate is uploaded to the Connected App. The integration user must be pre-authorized (through a permission set or the app's policies) so no interactive consent is needed.&lt;/p&gt;

&lt;p&gt;First generate a key pair and upload &lt;code&gt;server.crt&lt;/code&gt; to the Connected App under "Use digital signatures":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 730 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="nt"&gt;-keyout&lt;/span&gt; server.key &lt;span class="nt"&gt;-out&lt;/span&gt; server.crt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=salesforce-ci"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then build and sign the JWT, and exchange it for a token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/auth/jwt.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createSign&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./oauth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/=/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildAssertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;privateKeyPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RS256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;iss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// Connected App consumer key&lt;/span&gt;
    &lt;span class="na"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// user to impersonate&lt;/span&gt;
    &lt;span class="na"&gt;aud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// login.salesforce.com or test.salesforce.com&lt;/span&gt;
    &lt;span class="na"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 3 minutes&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signingInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;privateKeyPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RSA-SHA256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signingInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;signingInput&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;base64url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loginWithJwt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;privateKeyPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;server.key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assertion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildAssertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;privateKeyPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth2/token`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;urn:ietf:params:oauth:grant-type:jwt-bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;assertion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`JWT login failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issued_at&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="na"&gt;tokenType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token_type&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JWT is signed with your private key and verified by Salesforce against the uploaded certificate. Because there is no secret in transit and the assertion expires in three minutes, this flow is far safer for shared CI runners than storing a password.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client credentials flow
&lt;/h3&gt;

&lt;p&gt;When you want a token tied to a fixed run-as user with no user impersonation logic, the client credentials flow is the cleanest. You enable it on the Connected App and designate a run-as user. The request needs only the consumer key and secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loginWithClientCredentials&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/services/oauth2/token`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Client credentials login failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;issuedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issued_at&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="na"&gt;tokenType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token_type&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whichever flow you choose, the shape of the result is identical: an access token plus an instance URL. Everything downstream depends only on &lt;code&gt;SalesforceSession&lt;/code&gt;, so you can swap flows per environment — password locally, JWT in CI — without touching a single test.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The Salesforce REST API: CRUD, SOQL, and upsert
&lt;/h2&gt;

&lt;p&gt;With a session in hand, all REST calls target &lt;code&gt;{instanceUrl}/services/data/{apiVersion}/&lt;/code&gt;. Let's exercise the full lifecycle of an sObject.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a record
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loginWithPassword&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth/oauth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loginWithPassword&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;extraHTTPHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Playwright Test Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 201 Created, body: { id, success, errors }&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful create returns a 201 with the new record's Id. That Id drives every subsequent operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying with SOQL
&lt;/h3&gt;

&lt;p&gt;SOQL runs through a GET with a URL-encoded query string. Playwright encodes query params for you when you pass them as an object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/query`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT Id, Name, Industry FROM Account WHERE Id = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// result.totalSize, result.done, result.records[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For large result sets Salesforce paginates. When &lt;code&gt;done&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;, follow &lt;code&gt;result.nextRecordsUrl&lt;/code&gt; until it is exhausted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;queryAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;soql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/query`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;soql&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextRecordsUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updating and deleting
&lt;/h3&gt;

&lt;p&gt;Updates use PATCH against the record URL and return 204 No Content on success. Deletes use DELETE and also return 204.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Upsert on an external ID
&lt;/h3&gt;

&lt;p&gt;Integrations rarely know Salesforce Ids; they know their own keys. Upsert against an external ID field creates or updates in a single idempotent call. A 201 means the record was created, a 204 means it already existed and was updated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upsert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/External_Id__c/EXT-1001`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Upserted Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Retail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// upsert.status() === 201 (created) or 204 (updated)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A complete REST test
&lt;/h3&gt;

&lt;p&gt;Pulling it together into a Playwright spec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/rest/account-lifecycle.api.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loginWithPassword&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth/oauth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;createdId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loginWithPassword&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;extraHTTPHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;createdId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;createdId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creates, reads, updates and deletes an Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lifecycle Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;createdId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;createdId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;createdId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;createdId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;Industry&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single test verifies create, read, update, and the persistence of the update — the core contract of any sObject.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Composite APIs: batching, dependent requests, and trees
&lt;/h2&gt;

&lt;p&gt;Making one HTTP call per record wastes time and API quota. Salesforce offers a family of Composite endpoints that bundle many operations into a single round trip. There are three you will reach for constantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composite: dependent subrequests
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/composite&lt;/code&gt; endpoint runs up to 25 subrequests in order, and — crucially — later subrequests can reference the output of earlier ones using &lt;code&gt;@{referenceId.field}&lt;/code&gt; syntax. This lets you create a parent and child in one atomic call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;composite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/composite`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;allOrNone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;compositeRequest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newAccount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Composite Parent Inc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Contact`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;newContact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Composite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@{newAccount.id}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;composite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// body.compositeResponse[] — one entry per subrequest, in order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;allOrNone: true&lt;/code&gt;, if any subrequest fails the whole batch rolls back. This is exactly what you want when setting up related test data: you never end up with an orphaned parent because the child failed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composite batch: independent subrequests
&lt;/h3&gt;

&lt;p&gt;When subrequests are independent and you just want to save round trips, &lt;code&gt;/composite/batch&lt;/code&gt; runs up to 25 unrelated requests. There is no cross-referencing, and each succeeds or fails on its own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/composite/batch`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;batchRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idA&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;idB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  sObject Tree: nested record graphs
&lt;/h3&gt;

&lt;p&gt;To insert a parent with multiple children in one payload, the &lt;code&gt;/composite/tree/{sObject}&lt;/code&gt; endpoint accepts up to 200 records with nested child relationships.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/composite/tree/Account`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acc1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tree Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;Contacts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;con1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Child One&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;referenceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;con2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Child Two&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  sObject Collections: bulk-ish CRUD without a job
&lt;/h3&gt;

&lt;p&gt;For up to 200 records of the same operation, sObject Collections give you Bulk-like throughput with synchronous simplicity. POST to &lt;code&gt;/composite/sobjects&lt;/code&gt; to insert many at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/composite/sobjects`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;allOrNone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bulk Insert 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bulk Insert 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bulk Insert 3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// returns an array of { id, success, errors } in input order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule of thumb: use &lt;code&gt;/composite&lt;/code&gt; when records depend on each other, &lt;code&gt;/composite/tree&lt;/code&gt; for nested graphs, and &lt;code&gt;/composite/sobjects&lt;/code&gt; for a flat list of the same object. All three collapse many HTTP calls into one, which matters enormously when your suite is creating thousands of records.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Bulk API 2.0: ingest and query jobs
&lt;/h2&gt;

&lt;p&gt;When you are loading tens of thousands of records — or querying millions — the synchronous APIs will not do. Bulk API 2.0 is asynchronous and CSV-based. You create a job, upload data, close the job, and poll until Salesforce finishes processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ingest: loading records
&lt;/h3&gt;

&lt;p&gt;The ingest flow has four steps. Create the job, PUT the CSV, patch the state to &lt;code&gt;UploadComplete&lt;/code&gt;, then poll for completion.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/clients/bulk-client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BulkClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;ingestCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Create the ingest job&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/ingest`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// insert | update | upsert | delete&lt;/span&gt;
          &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CSV&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;lineEnding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Upload the CSV payload&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/batches`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Mark the job as ready for processing&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UploadComplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Poll until the job finishes&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pollJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;pollJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;JobComplete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Aborted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bulk job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ended in state &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bulk job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; did not complete within timeout`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getSuccessfulResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/successfulResults`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// CSV of results&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getFailedResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/ingest/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/failedResults`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using it in a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bulk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BulkClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name,Industry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bulk Corp A,Technology&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bulk Corp B,Finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bulk Corp C,Retail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ingestCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;insert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberRecordsProcessed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numberRecordsFailed&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always assert on both &lt;code&gt;numberRecordsProcessed&lt;/code&gt; and &lt;code&gt;numberRecordsFailed&lt;/code&gt;. A Bulk job can complete "successfully" while silently failing half its rows on validation rules — the job state is &lt;code&gt;JobComplete&lt;/code&gt; either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query: extracting large data sets
&lt;/h3&gt;

&lt;p&gt;For pulling large volumes out, the Bulk query flow mirrors ingest: create a query job, poll, then download results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;bulkQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;soql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;createRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/query`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;soql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;createRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// poll /jobs/query/{id} until state === 'JobComplete'&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pollQueryJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/jobs/query/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/results`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// CSV&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bulk API is the right tool when volume is high and latency is acceptable. For a handful of records, the synchronous Composite endpoints are simpler and faster. Choose deliberately based on data size.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. The Tooling API: metadata queries and anonymous Apex
&lt;/h2&gt;

&lt;p&gt;The Tooling API exposes the org's metadata as queryable objects and lets you run anonymous Apex. This is invaluable in testing: you can assert that a validation rule exists, check that a field has the expected metadata, or execute a snippet of Apex to set up state that no standard API exposes.&lt;/p&gt;

&lt;p&gt;Everything lives under &lt;code&gt;/services/data/{apiVersion}/tooling/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying metadata
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/clients/tooling-client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ToolingClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;soql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tooling/query`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;soql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Tooling query failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assert that a required Apex class is deployed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tooling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ToolingClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tooling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT Id, Name FROM ApexClass WHERE Name = 'AccountService'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;classes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or verify that a validation rule is active before running tests that depend on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tooling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT Id, ValidationName, Active FROM ValidationRule &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WHERE EntityDefinition.QualifiedApiName = 'Account'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running anonymous Apex
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;executeAnonymous&lt;/code&gt; endpoint runs an Apex snippet in the org's context. This is the escape hatch for setup that no declarative API covers — resetting a custom setting, invoking a batch class, or forcing a record into a specific state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;executeApex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apexBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tooling/executeAnonymous`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;anonymousBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apexBody&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compiled&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Apex failed. Compiled=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compiled&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="s2"&gt;`Line=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compileProblem&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exceptionMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tooling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeApex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`Account a = new Account(Name='Apex Seeded', Industry='Energy'); insert a;`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handle the response carefully. &lt;code&gt;executeAnonymous&lt;/code&gt; returns HTTP 200 even when the Apex fails to compile or throws — you must inspect &lt;code&gt;compiled&lt;/code&gt;, &lt;code&gt;success&lt;/code&gt;, &lt;code&gt;compileProblem&lt;/code&gt;, and &lt;code&gt;exceptionMessage&lt;/code&gt; yourself. The helper above turns any of those into a thrown error so your test fails loudly instead of continuing on bad state.&lt;/p&gt;

&lt;p&gt;Use the Tooling API sparingly and deliberately. It is powerful, but leaning on anonymous Apex for everything makes tests opaque. Prefer the standard REST and Composite APIs, and reach for Tooling only when nothing else can set up the state you need.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Building a reusable API client
&lt;/h2&gt;

&lt;p&gt;Scattering raw &lt;code&gt;api.post(...)&lt;/code&gt; calls across dozens of specs is a maintenance trap. Wrap the platform in a thin client that owns authentication, the base URL, versioning, and error handling. Tests then read as intent, not plumbing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/clients/salesforce-client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loginWithPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth/oauth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BulkClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bulk-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ToolingClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tooling-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesforceClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;APIRequestContext&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceSession&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;loginWithPassword&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;extraHTTPHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`sobjects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Create &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`sobjects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Retrieve &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`sobjects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Update &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`sobjects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Delete &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;soql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;soql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextRecordsUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;BulkClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BulkClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;tooling&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ToolingClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ToolingClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a test reads cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Client Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Client Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client centralizes every cross-cutting concern. Change the API version, swap the auth flow, or add a default header, and every test benefits without edits. This is the single most important structural decision in an enterprise suite.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Fixtures: injecting authenticated clients into tests
&lt;/h2&gt;

&lt;p&gt;Creating a &lt;code&gt;SalesforceClient&lt;/code&gt; in every &lt;code&gt;beforeAll&lt;/code&gt; is repetitive and logs in far more than necessary. Playwright fixtures solve this elegantly — you define the client once, and the runner injects it into any test that asks for it, sharing a single authenticated session per worker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/fixtures/salesforce.fixture.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@clients/salesforce-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;loginWithJwt&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth/jwt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Fixtures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Worker-scoped: one login shared across all tests in a worker.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;WorkerFixtures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sfWorker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Fixtures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WorkerFixtures&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;sfWorker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;loginWithJwt&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;worker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sfWorker&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sfWorker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests now import from the fixture and receive a ready-to-use client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/rest/opportunity.api.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/salesforce.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creates an Opportunity tied to an Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fixture Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oppId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Q1 Deal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prospecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-03-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oppId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prospecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;oppId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker-scoped fixture authenticates once per parallel worker rather than once per test. With four workers and two hundred tests, you go from two hundred logins to four. That is a meaningful reduction in both runtime and API consumption — Salesforce counts every login against org limits.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Combining API setup with UI validation
&lt;/h2&gt;

&lt;p&gt;The highest-value pattern in Salesforce test automation is hybrid: set up state through the API, then verify behavior through the UI. The API builds a complex data graph in milliseconds; the browser test then focuses purely on what the user actually sees.&lt;/p&gt;

&lt;p&gt;Consider verifying that a newly created Opportunity appears on its Account's related list in Lightning. Building the Account and Opportunity through the UI would take thirty seconds and a dozen brittle clicks. Through the API it takes one call, and the UI test does only what UI tests are good at.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/ui/opportunity-related-list.ui.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/salesforce.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shows the Opportunity on the Account related list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// --- Setup via API (fast, deterministic) ---&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hybrid Test Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Visible Deal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;StageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prospecting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;CloseDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-06-30&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// --- Authenticate the browser session using the same token ---&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/secur/frontdoor.jsp`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`?sid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`&amp;amp;retURL=/lightning/r/Account/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/view`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// --- Verify via UI (the part that needs a browser) ---&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Visible Deal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// --- Teardown ---&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`SELECT Id FROM Opportunity WHERE AccountId = '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;opps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Opportunity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;frontdoor.jsp&lt;/code&gt; trick logs the browser in with the OAuth access token you already have, skipping the login screen entirely. Your UI test starts already authenticated on the exact record page you care about. This single technique cuts UI test setup time dramatically and eliminates the flakiest part of most Salesforce browser suites — the login flow.&lt;/p&gt;

&lt;p&gt;The principle generalizes: any state that a user would laboriously build up should be created by the API, so the browser only ever exercises the behavior genuinely under test.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Contract testing against the Salesforce schema
&lt;/h2&gt;

&lt;p&gt;Integrations break when the org's schema drifts — a field is deleted, a picklist value removed, a required field added. Contract tests catch this by asserting on the describe metadata Salesforce exposes for every object. When a field your integration depends on disappears, the contract test fails immediately, long before a partner's nightly load does.&lt;/p&gt;

&lt;p&gt;The describe endpoint returns full metadata for an sObject:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SF_API_VERSION&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/describe`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A contract test asserting that the fields your integration relies on still exist with the expected types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tests/contract/account-contract.api.spec.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/salesforce.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@config/environment&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account exposes the fields the integration depends on&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`/services/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sobjects/Account/describe`&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fieldsByName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requiredContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Industry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;picklist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AnnualRevenue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;External_Id__c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;requiredContract&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fieldsByName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Field &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is missing`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Field &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; changed type`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can extend this to assert on picklist values, on whether a field is createable or updateable, or on required flags. Contract tests are cheap to run and catch an entire class of production incidents that functional tests miss, because they verify the shape of the schema rather than any single behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Error handling, retries, and rate limits
&lt;/h2&gt;

&lt;p&gt;Salesforce enforces API limits, and transient errors happen. Production-grade tests treat error handling as a first-class concern, not an afterthought.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the standard error format
&lt;/h3&gt;

&lt;p&gt;Salesforce errors come back as an array of objects with an &lt;code&gt;errorCode&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt;. Parse them consistently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertOk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;APIResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// leave detail as raw text&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrying transient failures
&lt;/h3&gt;

&lt;p&gt;A 500, 503, or a &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt; can be worth retrying with backoff. Wrap network operations in a small retry helper — but never retry non-idempotent creates blindly, or you will create duplicates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;baseDelayMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;lastError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;lastError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseDelayMs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;lastError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Respecting API limits
&lt;/h3&gt;

&lt;p&gt;Salesforce returns your remaining daily API allowance in the &lt;code&gt;Sforce-Limit-Info&lt;/code&gt; response header. In a large suite it is worth watching this so you fail with a clear message rather than a cryptic &lt;code&gt;REQUEST_LIMIT_EXCEEDED&lt;/code&gt; deep in a run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;logRemainingApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;APIResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sforce-limit-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// e.g. "api-usage=10432/15000"&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/api-usage=&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\/(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Low Salesforce API allowance: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; calls left`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Composite and Bulk patterns from earlier are your first defense against limits: fewer, fatter calls consume less quota than many small ones. When a suite starts bumping into daily limits, batching is almost always the fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Test data lifecycle and cleanup
&lt;/h2&gt;

&lt;p&gt;Tests that leave data behind poison the next run and slowly fill the org. Every record a test creates must be tracked and removed. The cleanest approach is a per-test registry that deletes in reverse dependency order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/fixtures/tracker.fixture.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/salesforce.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@clients/salesforce-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreatedRecordTracker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SalesforceClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Delete in reverse creation order so children go before parents.&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rec&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sobject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreatedRecordTracker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sf&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tracker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreatedRecordTracker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tracker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any test using this fixture gets automatic cleanup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@fixtures/tracker.fixture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cleans up automatically&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Account&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tracked Corp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contactId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tracked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;AccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contactId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// No manual delete — the fixture removes the Contact then the Account.&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because deletion runs in reverse order, children are removed before their parents, avoiding "cannot delete — child records exist" errors. Reverse-order teardown is a small detail that prevents a large category of flaky cleanup failures.&lt;/p&gt;

&lt;p&gt;For suites that occasionally leave orphans anyway — a crashed worker, a killed pipeline — schedule a periodic sweep that deletes records matching a test-data naming convention (for example, names ending in &lt;code&gt;Corp&lt;/code&gt; or tagged with a custom &lt;code&gt;Test_Run__c&lt;/code&gt; field) so the org never accumulates junk.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. CI/CD: GitHub Actions and Azure DevOps
&lt;/h2&gt;

&lt;p&gt;Tests deliver value only when they run automatically on every change. Both major platforms integrate cleanly with Playwright. The key concerns are the same in each: install dependencies, provide secrets, restore the JWT key, run the API project, and publish results.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/salesforce-api-tests.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Salesforce API Tests&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore JWT signing key&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "${{ secrets.SF_JWT_KEY }}" &amp;gt; server.key&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run API tests&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;CI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://test.salesforce.com&lt;/span&gt;
          &lt;span class="na"&gt;SF_API_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v62.0&lt;/span&gt;
          &lt;span class="na"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_CLIENT_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SF_USERNAME }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright test --project=api&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish HTML report&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;playwright-report/&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JWT private key is stored as a repository secret and written to &lt;code&gt;server.key&lt;/code&gt; at runtime — it never lives in the repo. Because CI uses the JWT flow, no password is needed, and the &lt;code&gt;if: always()&lt;/code&gt; on the artifact step ensures you get the report even when tests fail (which is exactly when you most want it).&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure DevOps
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# azure-pipelines.yml&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodeTool@0&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;versionSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20.x'&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "$(SF_JWT_KEY)" &amp;gt; server.key&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore JWT key&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx playwright test --project=api&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run API tests&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;CI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;SF_LOGIN_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://test.salesforce.com&lt;/span&gt;
      &lt;span class="na"&gt;SF_API_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v62.0&lt;/span&gt;
      &lt;span class="na"&gt;SF_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(SF_CLIENT_ID)&lt;/span&gt;
      &lt;span class="na"&gt;SF_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(SF_USERNAME)&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishTestResults@2&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;testResultsFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JUnit'&lt;/span&gt;
      &lt;span class="na"&gt;testResultsFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;results/junit.xml'&lt;/span&gt;
      &lt;span class="na"&gt;testRunTitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Salesforce&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tests'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JUnit reporter we configured in &lt;code&gt;playwright.config.ts&lt;/code&gt; feeds directly into Azure's &lt;code&gt;PublishTestResults&lt;/code&gt; task, giving you a native test-results tab with pass/fail trends over time. Secrets are stored as pipeline variables marked secret, mirroring the GitHub Actions setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline strategy
&lt;/h3&gt;

&lt;p&gt;Structure the pipeline in stages. Run the fast API project on every pull request as a gate. Run the slower UI project on merges to main or on a schedule. This keeps feedback tight for developers while still exercising the full browser suite regularly. Because the API and UI projects are already separated in the Playwright config, splitting stages is just two &lt;code&gt;--project&lt;/code&gt; invocations.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Best practices checklist
&lt;/h2&gt;

&lt;p&gt;Everything above distills into a set of principles worth keeping in front of you:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer JWT bearer for CI.&lt;/strong&gt; No password to rotate, short-lived assertions, and no interactive consent. Reserve the username-password flow for local development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authenticate once per worker.&lt;/strong&gt; Worker-scoped fixtures turn hundreds of logins into a handful, saving time and API quota that Salesforce meters aggressively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch aggressively.&lt;/strong&gt; Composite and Bulk endpoints collapse many calls into one. This is faster and consumes far less of your daily API allowance than record-by-record operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set up data by API, verify by UI.&lt;/strong&gt; Never build complex state through the browser. Let the API construct the graph, then use &lt;code&gt;frontdoor.jsp&lt;/code&gt; to drop the browser onto the exact page under test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Track and clean up every record.&lt;/strong&gt; A registry that deletes in reverse dependency order keeps the org clean and prevents cross-test contamination. Add a periodic sweep for orphans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assert on Bulk sub-results, not just job state.&lt;/strong&gt; A job can complete while silently failing rows. Check &lt;code&gt;numberRecordsFailed&lt;/code&gt;, and read the failed-results CSV when it is non-zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add contract tests.&lt;/strong&gt; Describe-based assertions catch schema drift — deleted fields, changed types, removed picklist values — before integrations break in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handle the real error format.&lt;/strong&gt; Salesforce returns error arrays with &lt;code&gt;errorCode&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt;. Parse them and surface them in failures so debugging is fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch the limit header.&lt;/strong&gt; &lt;code&gt;Sforce-Limit-Info&lt;/code&gt; tells you how much API allowance remains. Warn early rather than failing cryptically deep in a run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pin the API version in config.&lt;/strong&gt; One place to change when a new Salesforce release lands, and no surprises from default-behavior shifts between versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Where to go next
&lt;/h2&gt;

&lt;p&gt;You now have a complete, code-backed foundation: OAuth across three flows, the full REST lifecycle, Composite batching and trees, asynchronous Bulk jobs, Tooling API metadata and Apex, a reusable client, worker-scoped fixtures, hybrid API+UI validation, contract testing, error handling, disciplined cleanup, and CI/CD on both major platforms. That is the core of an enterprise-grade Salesforce API testing framework.&lt;/p&gt;

&lt;p&gt;The natural next steps are deepening each area: parameterizing tests across sandboxes, building a data-factory layer on top of the tracker, adding Apex REST endpoint testing for your custom services, layering in performance thresholds on API response times, and integrating test results into your release gates so a schema break blocks a deploy automatically.&lt;/p&gt;

&lt;p&gt;If you want the full, structured path — from your first Playwright test through enterprise framework design and complete API coverage — that is exactly what the book series below was written to give you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get the complete series
&lt;/h2&gt;

&lt;p&gt;This article covers the essentials, but the full material goes much deeper: complete framework architectures, hundreds of runnable code examples, page-object and fixture design patterns, parallel-execution tuning, reporting, and production-ready CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Salesforce Automation Testing Mastery Series (2026 Edition)&lt;/strong&gt; — three books, one bundle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Book 1 — Salesforce Testing with Playwright + TypeScript:&lt;/strong&gt; from zero to Automation Engineer. Lightning UI, page-object design, SOQL basics, and end-to-end scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Book 2 — Enterprise Salesforce Framework Design:&lt;/strong&gt; scalable framework architecture, fixtures and utilities, parallel execution, and CI/CD integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Book 3 — Salesforce API Testing with Playwright + TypeScript:&lt;/strong&gt; OAuth, REST, Composite, Bulk, and Tooling APIs, plus API + UI validation — the full depth behind this article.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Get the bundle:&lt;/strong&gt; &lt;a href="https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries" rel="noopener noreferrer"&gt;https://himanshuai.gumroad.com/l/SalesforceAutomationTestingMasterySeries&lt;/a&gt;&lt;br&gt;
Apply code &lt;strong&gt;&lt;code&gt;JUPITER80&lt;/code&gt;&lt;/strong&gt; at checkout for the launch discount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have questions or want to talk shop?&lt;/strong&gt; Connect with me on LinkedIn: &lt;a href="https://www.linkedin.com/in/himanshuai/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/himanshuai/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build. Automate. Scale. Deliver — like an enterprise.&lt;/p&gt;

</description>
      <category>api</category>
      <category>playwright</category>
      <category>testing</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Own Your AI: The Complete Guide to Running LLMs Locally with Ollama — Privacy, Zero API Costs, and Total Control</title>
      <dc:creator>Himanshu Agarwal</dc:creator>
      <pubDate>Tue, 28 Jul 2026 13:45:11 +0000</pubDate>
      <link>https://dev.to/himanshuai/own-your-ai-the-complete-guide-to-running-llms-locally-with-ollama-privacy-zero-api-costs-and-1ljd</link>
      <guid>https://dev.to/himanshuai/own-your-ai-the-complete-guide-to-running-llms-locally-with-ollama-privacy-zero-api-costs-and-1ljd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;🚀 &lt;strong&gt;Ready to own your AI stack?&lt;/strong&gt; Get the complete four-book journey here → &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;Ollama &amp;amp; Local LLMs — The Complete 4-Book Series&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;📚 &lt;strong&gt;Explore 150+ ebooks&lt;/strong&gt; on AI, engineering, and building things that matter → &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com" rel="noopener noreferrer"&gt;himanshuai.gumroad.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;💬 &lt;strong&gt;Want a discount, or have a purchase question?&lt;/strong&gt; Connect with me directly on &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/himanshuai/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/strong&gt; and let's talk.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;There is a quiet revolution happening on ordinary computers. While most of the world sends its questions, documents, and ideas to distant servers owned by a handful of companies, a growing number of people have realized something powerful: you don't need any of that. The same kind of artificial intelligence that powers the famous cloud assistants can run on the laptop already sitting on your desk, on a small server humming in the corner of a room, or on a modest machine you bought for a few hundred dollars. It can run privately, offline, and without a single cent of per-use cost. This is local AI, and once you understand it, the way you think about artificial intelligence changes permanently.&lt;/p&gt;

&lt;p&gt;This article is a complete, plain-language introduction to that world. It explains why running large language models locally has become one of the most important skills of the decade, how the technology actually works beneath the marketing, and the practical path from your very first local model to a system that can serve an entire team or organization. Whether you are a privacy-conscious individual, a developer tired of watching API invoices climb, a hobbyist who loves to tinker, or an engineer tasked with bringing AI into a company that cannot send its data to third parties, there is a place for you here. By the end, you'll understand not just what local AI is, but why it matters, what it can do for you, and exactly how to begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Renting Your Intelligence
&lt;/h2&gt;

&lt;p&gt;Cloud AI is genuinely convenient. You open an app, type a question, and get an answer. There is nothing to install, nothing to configure, and nothing to maintain. For a great many people, most of the time, that is enough. But convenience has a way of hiding its true price, and the price of cloud AI is steeper than it first appears — steep in three distinct and permanent ways.&lt;/p&gt;

&lt;p&gt;The first cost is your data. Every prompt you type, every document you upload, and every answer you receive passes through infrastructure you do not own and cannot inspect. Depending on the terms of service, that text may be stored, analyzed, and used to train future models. For casual questions this may not trouble you. But the moment you paste in a legal contract, a medical record, a piece of unreleased code, a private journal entry, or a customer's personal information, you have handed that data to a third party. You are trusting that it will be handled well, that the company will not suffer a breach, that it will not be compelled to hand it over, and that the rules governing it will not change. That is a great deal of trust to place in something you cannot see.&lt;/p&gt;

&lt;p&gt;The second cost is money, and it is a cost that never stops. Cloud AI is metered. You pay per token, per request, per seat, per month — forever. For light personal use the bills are small, but they add up, and for anything heavy or automated they can become substantial. A developer who wires an AI model into a workflow that runs thousands of times a day can watch a modest experiment turn into a serious operating expense. Worse, the meter shapes your behavior: you hesitate to experiment, to run large batches, to let an idea run wild, because every keystroke has a cost attached. Rented intelligence is intelligence you are always a little afraid to use.&lt;/p&gt;

&lt;p&gt;The third cost is control, or rather the lack of it. When you build on a hosted model, you are building on ground that can shift without warning. The provider can change its pricing, tighten its policies, alter the model's behavior overnight, deprecate the version you depend on, or restrict access in ways you never anticipated. You have no recourse, because you never owned any of it. Your capability lives at the pleasure of a company whose incentives are not the same as yours. For a hobby that may be fine. For anything you depend on, it is a quiet fragility running underneath everything you build.&lt;/p&gt;

&lt;p&gt;Local AI answers all three of these costs at once, and that is the heart of why it matters. When the model runs on your own hardware, your data never leaves your control — there is no third party to retain it, breach it, or hand it over. There is no meter, because the computation is yours and costs only the electricity to run it. And there is no lock-in, because the model, the software, and the data are all things you own outright. You are no longer renting your intelligence. You own it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Ollama: Local AI Made Approachable
&lt;/h2&gt;

&lt;p&gt;For years, running a language model on your own machine was the preserve of a small circle of specialists. It meant wrestling with source code, hunting for the right files, deciphering cryptic configuration, and assembling a working system from scattered parts. The capability existed, but the barrier to entry was high enough that most people never crossed it.&lt;/p&gt;

&lt;p&gt;That barrier is gone, and the tool that removed it is Ollama. Ollama is a piece of software that makes running local models about as simple as installing any ordinary application. You download it, and with a single command you can pull a model onto your machine and start talking to it. The model runs entirely on your hardware. Nothing leaves your computer. There is no account to create, no API key to manage, no bill to watch. It simply works.&lt;/p&gt;

&lt;p&gt;What makes Ollama special is not only that it runs models, but that it hides the complexity that used to make local AI painful. It handles the fiddly details of packaging a model — the weights, the tokenizer, the prompt formatting — so that you never have to think about them. It runs quietly in the background as a service, ready whenever you need it, and it exposes a clean interface so that other programs can talk to it. It manages your models the way a package manager manages software: pull the ones you want, run them, remove the ones you don't. In short, Ollama took something that used to require expert knowledge and turned it into something an ordinary curious person can set up in an afternoon.&lt;/p&gt;

&lt;p&gt;But approachable does not mean shallow. Beneath Ollama's simplicity lies a rich and powerful world, and the more you understand it, the more you can do. That understanding is exactly what separates someone who can run a default model from someone who can bend local AI to their will — fitting bigger models onto modest hardware, building private services, and scaling to serve many people. The journey from the first to the second is what the rest of this article maps out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Idea That Governs Everything: Memory
&lt;/h2&gt;

&lt;p&gt;If you learn only one thing about local AI, learn this: the single most important factor in what your hardware can run is memory. Not processor speed, not the number of cores, not any of the specifications people usually obsess over — memory. Understanding this one idea will save you more frustration than any other piece of knowledge, and it is the foundation on which everything else rests.&lt;/p&gt;

&lt;p&gt;A language model is, at its core, an enormous collection of numbers called weights — often billions of them. To run the model, all of those numbers must be held in fast memory, along with a working space for the conversation the model is currently processing. If the model and its working space fit comfortably in your fast memory, everything runs quickly. If they do not fit, the system is forced to shuffle data back and forth from slower storage, and performance collapses from brisk to painfully sluggish. The wall between "fast and delightful" and "slow and frustrating" is almost always the memory wall.&lt;/p&gt;

&lt;p&gt;This has a practical consequence that shapes every decision you make. A bigger model is generally more capable, but it also demands more memory. So the art of local AI is a constant, deliberate balancing act: choosing a model large enough to be genuinely useful, but small enough to fit in the memory you actually have, with room to spare for the conversation. Once you internalize this, the confusing landscape of model choices becomes navigable. You stop asking "what is the best model?" and start asking "what is the best model that fits my machine?" — which is the right question, and one you can actually answer.&lt;/p&gt;

&lt;p&gt;Different kinds of hardware handle this differently, and that matters too. A dedicated graphics processor holds models in its own fast video memory and accelerates them dramatically, which is why gaming machines and workstations make excellent local AI hosts. Apple's recent computers use a clever unified memory design that lets them punch far above their weight for their price and power draw, making them surprisingly strong local AI machines. And ordinary computers without a graphics processor can still run smaller models using system memory, more slowly but perfectly usably. There is a viable path on almost any hardware — you simply have to match the model to the machine, and the memory rule is how you do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Secret Weapon: Quantization
&lt;/h2&gt;

&lt;p&gt;Here is where local AI gets genuinely clever, and where the difference between a novice and someone who truly understands the field becomes stark. That memory wall we just discussed is not fixed. Its height depends on a choice — a choice most people make by accident and that you can instead make deliberately. That choice is called quantization, and it is the secret weapon of local AI.&lt;/p&gt;

&lt;p&gt;Remember that a model is a vast collection of numbers. Normally, each of those numbers is stored very precisely, which takes up a lot of space. Quantization is the art of storing those numbers less precisely — using fewer bits for each one — so that the whole model takes up far less memory. The remarkable and almost magical fact is that a model quantized sensibly loses very little of its capability. You can often cut a model's memory footprint in half, or even to a quarter, while it remains almost indistinguishable from the original for everyday work. The small loss in precision washes out across billions of numbers, leaving the model's knowledge and reasoning largely intact.&lt;/p&gt;

&lt;p&gt;The implications are enormous. Quantization is what lets you run a large, capable model on hardware that could never hold it at full precision. It is what turns "this model is too big for my computer" from a verdict into a question. It is what lets a person with a modest machine run models that would otherwise require expensive, specialized hardware. And it is why the same model can be offered in many different sizes, each trading a little quality for a lot of space, so you can pick exactly the balance your hardware and your task demand.&lt;/p&gt;

&lt;p&gt;Learning to work with quantization — to read the cryptic names that describe it, to choose the right one for your situation, and even to create your own — is one of the highest-leverage skills in all of local AI. It is the difference between leaving half of your hardware's potential unused and squeezing every drop of capability out of the machine you already own. It sounds intimidating, full of strange abbreviations and technical jargon, but the underlying ideas are not hard, and once they click, you never look at a model the same way again.&lt;/p&gt;

&lt;h2&gt;
  
  
  From a Tool You Run to a Service You Own
&lt;/h2&gt;

&lt;p&gt;Running a model on your own laptop is a wonderful thing, but it is only the beginning. For a single person at a single keyboard, it may be all you ever need. But something happens once you have tasted private AI: you want it everywhere. You want it on your phone from the sofa. You want your family to use it without touching a terminal. You want your team to query the company's documents without any of that text ever reaching a vendor. You want it running quietly on a machine in the corner, always on, always yours, always available.&lt;/p&gt;

&lt;p&gt;That is the leap from a program you run to a service you host, and it is a bigger leap than it first appears. A local model is a tool you invoke when you happen to be at the machine. A self-hosted service is infrastructure that other people and other devices depend on. It must stay up. It must be reachable, but only by the right people. It must protect the data poured into it by everyone who uses it. And — most excitingly — it can be connected to your own documents so that it answers from your knowledge rather than its generic training.&lt;/p&gt;

&lt;p&gt;Building this kind of private service is entirely achievable, and it follows a clear pattern. You put your model behind a durable, self-restarting system so that it survives reboots and comes back on its own after any hiccup. You place a polished, familiar web interface in front of it — one that looks and feels like the commercial chat products everyone already knows — so that non-technical people can use it comfortably from any device. You give it accounts and roles so that a household or team can share it while keeping each person's conversations private. And you secure the whole thing so that it is reachable from anywhere you need, yet never exposed to the open internet where it could be abused.&lt;/p&gt;

&lt;p&gt;The result is genuinely remarkable: a private replacement for the cloud assistants you were renting, running on hardware you control, usable by everyone you choose, and keeping every prompt and document inside a boundary you own. For a family, it is a shared assistant that respects everyone's privacy. For a small business, a law firm, a clinic, or a school, it can be the only acceptable way to use AI at all — because the data legally or ethically cannot leave the building. This is what "private AI" really means, and it is within reach of any determined person willing to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching Your AI About Your Own World
&lt;/h2&gt;

&lt;p&gt;There is one capability that transforms a private AI service from a clever chatbot into something genuinely indispensable, and it deserves special attention: the ability to answer from your own documents.&lt;/p&gt;

&lt;p&gt;A general model, however capable, knows a great deal about the world and nothing about you. It has never read your company handbook, your research notes, your project files, your family's records, or last quarter's report. Ask it about them and it will either admit ignorance or, worse, invent a confident answer. The technique that fixes this — and does so without sending your documents to anyone — is often called retrieval-augmented generation, and it is one of the most valuable things you can add to a private setup.&lt;/p&gt;

&lt;p&gt;The idea is elegant. Instead of hoping the model already knows something, you give it the relevant pieces of your own material at the exact moment you ask a question. Your documents are read, broken into passages, and organized so that the most relevant pieces can be found instantly. When you ask something, the system retrieves the passages that bear on your question and hands them to the model, which then answers based on your actual knowledge rather than its generic training. The model supplies the fluency and reasoning; your documents supply the facts. And crucially, all of this happens on your own hardware — your documents are read, searched, and answered without ever leaving your control.&lt;/p&gt;

&lt;p&gt;The effect is like giving your AI a perfect memory of everything you care about. Suddenly it can answer questions about your business's policies, summarize your research library, help you navigate your own notes, or draft replies grounded in your real correspondence. For an organization, it becomes an institutional memory: the company's collected knowledge, made instantly answerable by anyone with permission, entirely in-house. This single capability is often the reason people move from casually running a model to seriously building a private service, because it turns AI from a general-purpose novelty into a tool that knows your specific world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling Up: From One Person to Many
&lt;/h2&gt;

&lt;p&gt;For most people, a private service for themselves, their family, or a small team is the summit — and it is a fine place to stop. But some journeys keep climbing, and there is a whole further world for those who need to serve not a handful of people but dozens, hundreds, or an entire organization. This is where local AI grows up into production infrastructure, and it is a genuinely different discipline.&lt;/p&gt;

&lt;p&gt;The reason it is different comes down to a simple but profound fact: serving one user and serving many users are not the same problem. A setup that delights a single person can collapse the moment several people use it at once. When requests arrive one after another, a simple system handles them in turn, and each person waits a little longer than the last. Add enough simultaneous users and the waits become intolerable, then the system begins to fail entirely. The naive approach that works beautifully for one person simply does not scale, and no amount of wishful thinking changes that.&lt;/p&gt;

&lt;p&gt;Solving this requires a shift in both tools and thinking. There are specialized serving engines, built specifically for high concurrency, that use ingenious techniques to keep the hardware fully busy and to weave many simultaneous requests together efficiently. Where a simple engine handles requests one at a time, these engines pack many into the same work, achieving several times — and at high load, many times — the throughput on the very same hardware. Adopting one of these engines is the single biggest leap in capability when you outgrow a personal setup.&lt;/p&gt;

&lt;p&gt;Beyond the engine, real scaling brings a whole toolkit of ideas familiar to anyone who has run a serious online service. You spread load across multiple machines so that no single one is overwhelmed. You eliminate single points of failure so that one broken machine does not take the whole service down. You add capacity automatically as demand rises and shed it as demand falls, so you are neither overwhelmed at the peak nor wasting money at the trough. You watch the whole system with proper monitoring, so you learn of problems before your users do. And you plan capacity deliberately, sizing your hardware to real demand rather than guessing. None of this is magic, and all of it is learnable — it is simply the natural extension of everything that came before, applied at a larger scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Is This For? Everyone, at Their Own Level
&lt;/h2&gt;

&lt;p&gt;One of the most beautiful things about local AI is how the same core knowledge serves such a wide range of people. The hobbyist tinkering on a weekend and the engineer deploying to an enterprise are, at bottom, learning the same craft — they simply turn different dials.&lt;/p&gt;

&lt;p&gt;If you are a &lt;strong&gt;privacy-conscious individual&lt;/strong&gt;, local AI is how you use powerful models without ever surrendering your data. Your journal, your finances, your health questions, your private thoughts — all of it stays on your machine. You get the capability of modern AI with none of the surveillance, and that combination is impossible to buy at any price from a cloud provider.&lt;/p&gt;

&lt;p&gt;If you are a &lt;strong&gt;developer or builder&lt;/strong&gt;, local AI is how you escape the tyranny of the meter. You can wire models into your tools, run them in loops, batch enormous jobs, and experiment without fear, because the marginal cost of each request is essentially zero. Ideas that would be too expensive to try against a metered API become trivial to explore on hardware you already own. For anyone who builds, that freedom is transformative.&lt;/p&gt;

&lt;p&gt;If you are a &lt;strong&gt;hobbyist or learner&lt;/strong&gt;, local AI is simply one of the most fascinating and rewarding things you can tinker with. There is a deep satisfaction in running powerful AI on your own machine, understanding how it works, and pushing your hardware to do more than you thought possible. It is a hobby that teaches you real, valuable skills while being genuinely fun.&lt;/p&gt;

&lt;p&gt;And if you work in or with an &lt;strong&gt;organization&lt;/strong&gt;, local AI is often the only viable path to using this technology at all. Law firms, clinics, financial institutions, schools, government bodies, and countless companies handle data that legally or contractually cannot be sent to a third party. For them, a self-hosted, private AI service is not a preference but a requirement, and the ability to build and run one is an increasingly valuable professional skill. The crossover between hobbyist and enterprise is real: the same fundamentals that let an enthusiast run a model in their bedroom let a professional deploy one for an entire company.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clearing Up the Myths
&lt;/h2&gt;

&lt;p&gt;Because local AI is still unfamiliar to many, it attracts a few persistent myths worth dispelling directly.&lt;/p&gt;

&lt;p&gt;The first myth is that you need extraordinarily expensive hardware. You do not. While more powerful machines can run larger models, there is a viable path on very modest hardware, especially once you understand quantization. Many people are surprised to discover that a computer they already own, or an affordable machine bought secondhand, is perfectly capable of running genuinely useful models. The barrier is knowledge, not money.&lt;/p&gt;

&lt;p&gt;The second myth is that local models are hopelessly inferior to the big cloud ones. This was more true in the past than it is now. The open models available to run locally have improved at a staggering pace, and for a huge range of everyday tasks — writing, summarizing, answering questions, helping with code, brainstorming — a well-chosen local model is more than good enough. The gap that remains is at the very frontier, for the hardest tasks, and even there it narrows constantly. For the great majority of real work, local models deliver.&lt;/p&gt;

&lt;p&gt;The third myth is that setting all this up is impossibly technical. It used to be difficult, but tools like Ollama have transformed the experience. Getting a model running locally is now genuinely approachable for anyone willing to follow clear guidance. The deeper skills — self-hosting, quantization, scaling — take more effort, but each is learnable in plain language, one concept at a time, without any research background. The intimidation is mostly a leftover from an earlier, harder era.&lt;/p&gt;

&lt;p&gt;The fourth myth is that local AI is only for privacy extremists. Privacy is a wonderful benefit, but it is far from the only one. The cost savings alone justify local AI for heavy users. The freedom from lock-in justifies it for anyone building something they depend on. The ability to work offline, to customize deeply, and to truly own your tools — these appeal to a far broader audience than privacy alone. Local AI is simply a better arrangement for a great many purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Economics: Do the Math
&lt;/h2&gt;

&lt;p&gt;It is worth pausing on the money, because the economics of local AI are more compelling than most people realize, and they are worth understanding honestly rather than through hype.&lt;/p&gt;

&lt;p&gt;Cloud AI charges you per use, which feels cheap when you use it lightly and grows expensive as your use increases. Local AI works the opposite way: it asks for an investment up front — hardware, and your time to learn — and then makes each subsequent use nearly free. The crossover point, where owning becomes cheaper than renting, depends entirely on how heavily you use it. For someone who asks a few questions a day, a cloud subscription may never cost enough to justify buying hardware. But for a developer running automated workflows, a team using AI all day, or anyone whose usage is heavy and predictable, the math tips decisively toward owning. Past a certain volume, the marginal cost of a local query is just electricity, and that advantage widens every single day you use it.&lt;/p&gt;

&lt;p&gt;But the honest accounting goes beyond a simple comparison of hardware price against subscription fees. On the cost side, local AI includes the electricity to run a machine, occasional maintenance, and the value of your own time to set it up and keep it running. On the benefit side sit things that never appear on any invoice: privacy that has no cloud equivalent at any price, freedom from a meter that discourages experimentation, and independence from a provider's outages and policy changes. When you weigh the full picture, the conclusion is rarely about cost alone. You self-host when privacy, control, or heavy predictable use make it worthwhile — and you treat the favorable economics at scale as a welcome bonus rather than the whole argument.&lt;/p&gt;

&lt;p&gt;There is also a strategic dimension to the economics that is easy to miss. Skills compound. The time you invest in learning local AI does not evaporate; it becomes a durable capability that pays off across every model, every machine, and every project you touch for years to come. In a world where AI is becoming central to how work gets done, the ability to run it yourself — privately, cheaply, and under your own control — is an asset whose value only grows. That is an investment in yourself, not merely in a piece of software.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Roadmap: How to Actually Begin
&lt;/h2&gt;

&lt;p&gt;Knowing why local AI matters is one thing; knowing how to start is another. The good news is that the path is clear and each step builds naturally on the last. You do not have to leap to the summit — you climb one sensible step at a time.&lt;/p&gt;

&lt;p&gt;The first step is simply to get a model running on your own machine. Install Ollama, pull a small-to-mid-size model appropriate for your hardware, and start talking to it. This first experience — watching a capable AI respond entirely on your own computer, with no account and no internet dependency — is genuinely eye-opening, and it is the foundation for everything else. Spend time here. Get comfortable. Learn the memory rule by feeling it: try models of different sizes and notice where your machine stays fast and where it starts to strain.&lt;/p&gt;

&lt;p&gt;The second step is to learn to control what you run. Discover how to talk to your model programmatically so you can wire it into your own scripts and tools. Learn to customize a model's behavior to suit how you work. Understand how to choose the right model for a given task and machine. This is where you move from passively using a default to actively shaping your local AI to your needs, and it is where the real productivity begins.&lt;/p&gt;

&lt;p&gt;The third step, when you are ready, is to turn your local model into a private service. Put it behind a durable setup, add a friendly web interface, give it accounts for the people who will share it, and connect it to your own documents so it answers from your knowledge. Secure it so you can reach it safely from anywhere. This transforms local AI from a personal tool into shared infrastructure that others can rely on — and it is where local AI starts to replace the cloud services you were paying for.&lt;/p&gt;

&lt;p&gt;The fourth step, for those who need it, is to master the deeper crafts: quantization, to fit bigger and better models onto your hardware and to reclaim memory most people never touch; and scaling, to serve many users reliably when a personal setup is no longer enough. These are the advanced skills that separate someone who dabbles from someone who truly commands local AI, and they open the door to using it professionally, at real scale.&lt;/p&gt;

&lt;p&gt;At each step, the pattern is the same: learn the concept in plain language, put it into practice on your own machine, and build genuine capability before moving on. There is no rush, and there is no step you cannot reach with patience and clear guidance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete Journey, in Four Books
&lt;/h2&gt;

&lt;p&gt;Everything described in this article — from your first local model to a scaled, production-grade service — is exactly the path laid out in the &lt;strong&gt;Ollama &amp;amp; Local LLMs&lt;/strong&gt; four-book series by Himanshu Agarwal. Each book takes one stage of the journey and teaches it thoroughly, in plain language, with real commands and practical guidance, and each builds on the last so that together they form one complete arc.&lt;/p&gt;

&lt;p&gt;The series begins with &lt;strong&gt;Running LLMs Locally with Ollama&lt;/strong&gt;, which takes you from nothing to running real models on your own hardware, with the mental models and hands-on projects that make the knowledge stick. It teaches the all-important memory rule, how to talk to models over the interface, how to customize and control them, and how to build genuinely useful things — all while keeping your data entirely your own.&lt;/p&gt;

&lt;p&gt;From there, &lt;strong&gt;Private AI: Self-Hosting Language Models&lt;/strong&gt; turns that skill into an always-on private service. It shows you how to stand up a durable stack, put a polished multi-user interface in front of it, connect it to your own documents, and lock the whole thing down so that convenience never costs you the privacy that was the point. It is the blueprint for a private replacement for the cloud assistants you were renting.&lt;/p&gt;

&lt;p&gt;Next, &lt;strong&gt;The Local LLM Playbook: Ollama, GGUF, and Quantization&lt;/strong&gt; goes deep on the layer that decides what your hardware can truly do. It teaches you to read quantization names fluently, choose the right one for your machine and task every time, fit bigger and better models than you thought possible, make your own quantized models, and reclaim hidden memory by quantizing the context cache. It is the book that lets you extract every drop of capability from the hardware you own.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;From Laptop to Server: Scaling Local LLMs&lt;/strong&gt; carries everything into the world of scale. It explains why serving many users is a different problem, introduces the high-throughput engines built for concurrency, and covers load balancing, high availability, autoscaling, monitoring, and capacity planning — closing with a complete reference architecture. It is the finale that turns "it works on my machine" into "it works for everyone, reliably."&lt;/p&gt;

&lt;p&gt;Read together, the four books are a complete education in owning your AI, from the first curious experiment to running infrastructure an organization can depend on. Each is written to be approachable regardless of background, each includes a thorough resources section, and each is available in both PDF and EPUB formats for comfortable reading on any device, with instant download.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need to be a programmer to run local AI?&lt;/strong&gt; No. Getting a model running with Ollama is approachable for anyone willing to follow clear steps. Some of the deeper skills benefit from comfort at a command line, but every concept in the series is built up from the ground with no assumed background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will a local model be as good as the famous cloud ones?&lt;/strong&gt; For the vast majority of everyday tasks — writing, summarizing, answering questions, helping with code, brainstorming — a well-chosen local model is more than good enough, and the open models improve constantly. The gap that remains is at the extreme frontier, and even there it narrows all the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What hardware do I need?&lt;/strong&gt; Less than you think. There is a viable path on modest machines, and quantization stretches your hardware further still. The series teaches you exactly how to match a model to whatever machine you have, rather than demanding you buy something expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is my data really private with local AI?&lt;/strong&gt; Yes. When the model runs on your own hardware, your prompts and documents never leave your control. There is no third party to store, analyze, or hand over your data. The self-hosting book also covers how to keep it private in practice, not just in principle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I really save money compared to cloud AI?&lt;/strong&gt; For heavy or predictable use, absolutely — the marginal cost of a local query is essentially just electricity. For light use, the savings matter less, and the bigger benefits are privacy and control. The series is honest about exactly where the economics favor local AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I start?&lt;/strong&gt; Start with the first book, get a model running, and build from there one step at a time. The whole series is designed so that each stage prepares you for the next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Own It, Don't Rent It
&lt;/h2&gt;

&lt;p&gt;We stand at a moment where artificial intelligence is becoming woven into how we work, create, and think. The default path is to rent that intelligence from a handful of large companies — to send them your data, pay them by the token, and build on ground that can shift beneath you at any time. It is a convenient path, and for some it is the right one. But it is not the only one, and for a growing number of people it is not the best one.&lt;/p&gt;

&lt;p&gt;The alternative is to own your AI. To run it on hardware you control, with data that never leaves your walls, at a cost that does not climb with every use, free from any provider's changing rules. To build private services that respect the people who use them. To fit powerful models onto modest machines through skill rather than expense. And, when you need to, to scale it all up into infrastructure a whole organization can rely on. This is not a distant or exotic capability. It is available today, it is learnable by anyone willing to begin, and the tools have never been more approachable.&lt;/p&gt;

&lt;p&gt;The knowledge to do it well is the difference between being a passenger and being in the driver's seat of the most important technology of our time. That knowledge is exactly what the Ollama &amp;amp; Local LLMs series delivers — the complete journey, from your first local model to a scaled private service, in plain language, ready to put into practice. Whatever your reason — privacy, cost, control, curiosity, or profession — the path is open, and the first step is closer than you think.&lt;/p&gt;

&lt;p&gt;Own your AI. Your data, your hardware, your rules.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🚀 &lt;strong&gt;Start the complete journey today&lt;/strong&gt; → &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com/l/Ollama-Local-LLMs-The-Complete4-Book-Series" rel="noopener noreferrer"&gt;Ollama &amp;amp; Local LLMs — The Complete 4-Book Series&lt;/a&gt;&lt;/strong&gt; — all four books, PDF + EPUB, instant download.&lt;/p&gt;

&lt;p&gt;📚 &lt;strong&gt;Explore 150+ ebooks&lt;/strong&gt; on AI, engineering, and building things that matter → &lt;strong&gt;&lt;a href="https://himanshuai.gumroad.com" rel="noopener noreferrer"&gt;himanshuai.gumroad.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;💬 &lt;strong&gt;Looking for a discount, or have a question before you buy?&lt;/strong&gt; Connect with me directly on &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/himanshuai/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/strong&gt; — I'm happy to help.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written by Himanshu Agarwal · Own your AI: private, offline, and free of per-token costs.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
    </item>
  </channel>
</rss>
