Keyword-driven Automation with Playwright and TypeScript
Write native Step Keywords with Playwright and TypeScript, unit-test them locally, then build and run them on a Step cluster — reusing one Keyword library for both a functional test and a load test.
Get Step SaaS for free to follow this tutorial
This tutorial walks you through a complete keyword-driven Playwright project written in TypeScript: you will clone a ready-made sample, understand how its Keywords are built, run them locally as unit tests, then compile and run them on a Step cluster.
This tutorial covers the keyword-driven approach, where your Playwright code is written as native Step Keywords — functions that Step calls directly, with their own inputs, outputs and shared session. This is what lets a single Keyword library be reused across different Plans, and it gives you per-Keyword reporting in Step.
Playwright can also be integrated the other way round: keep an existing Playwright test suite as it is and have Step run it through the Execute Keyword, invoking a command such as npx playwright test or npm test. That command-based approach is better suited to importing an existing suite unchanged, and will be covered in a separate tutorial.
Prerequisites
- Node.js 24 or later, and npm, installed on your local machine
- Step CLI installed — see the Step CLI documentation
- Git, to clone the sample repository
- Access to a Step cluster. Get started quickly by setting up a SaaS cluster in the Step Portal — Node.js agents are available by default and auto-provisioned, so nothing else needs configuring. Alternatively, follow the Installation page to set up your own on-premise cluster, where a Node agent has to be registered.
Test scenario
The sample automates a purchase on our demo online store. The user journey is split into two Keywords:
findProduct— opens a browser, searches for a product, and reports its price as a Keyword outputcheckout— reuses the browser opened byfindProductto add the product to the cart and complete a guest checkout
The same two Keywords are then reused by two different Plans: a functional test case and a load test simulating concurrent users. This is one of the main benefits of the Keyword-driven approach — you automate the journey once and reuse it across testing purposes.
Checkout the sample
Clone the sample project from GitHub:
git clone https://github.com/exense/step-samples.git
Navigate to the sample directory:
cd step-samples/automation-packages/playwright-typescript
The project is organized as follows:
playwright-typescript/
├── automation-package.yaml # Automation Package descriptor: Plans, Parameters, Keyword declarations
└── nodejs-keywords/
├── src/
│ └── keywords-typescript.ts # Keyword implementations (TypeScript source)
├── tests/
│ └── keywords-typescript.test.ts # Local unit test of the two Keywords
├── keywords/ # Compiled JavaScript, generated by the build
├── package.json
├── tsconfig.json # Keyword build: compiles src/ to keywords/
└── tsconfig.test.json # Type-checks the tests, which are not part of the build
Install the dependencies
cd nodejs-keywords
npm install
postinstall script runs playwright install chromium, which downloads the browser binary Playwright drives. The first install therefore takes noticeably longer than a plain npm install.
Understanding the Keywords
The Keywords are implemented in nodejs-keywords/src/keywords-typescript.ts. Each one is an exported async function following the Step Keyword API signature:
export async function findProduct(input, output, session, properties) {
...
}
Those four arguments are the entire contract between Step and your automation code.
Inputs and properties
The sample deliberately splits the data it receives in two:
inputcarries the business data of the call —input.urlandinput.productName— provided per Keyword call in the Planpropertiescarries the technical configuration — hereproperties['headless']— declared once for the whole package and overridable in Step
const headless = properties['headless'] === 'true';
const browser = await chromium.launch({ headless });
=== 'true' comparison matters: a plain truthiness check would read the string "false" as true.
Sharing a browser between Keywords
session is a store shared by all Keywords running in the same session. findProduct registers the browser in it, and checkout picks the page back up:
// in findProduct
session.set('browser', browser);
session.set('playwright', { context, page });
// in checkout
const { context, page } = session.get('playwright');
Registering the browser is also all the cleanup the sample needs: Step closes every session value that exposes a close() method when the session ends, and closing a browser closes its contexts and pages with it. The playwright entry is a plain object with nothing to close, so Step leaves it alone and the browser remains the single owner of the cleanup.
Reporting results
output is the channel back to Step. output.add('price', price) returns a Keyword output that the Plan can use downstream, and output.attach(...) attaches a file to the execution report — here a Playwright trace:
const price = await page.getByRole('heading', { name: '$' }).textContent();
output.add('price', price);
finally block, so the trace is attached when the Keyword fails too — which is exactly when it is most useful. The helper writes each trace into a private mkdtemp directory before attaching it: under load, several Keywords run concurrently on the same agent and share the OS temp directory, so a fixed file name would let one execution overwrite another’s trace.
For a complete description of the API, see the Step Keyword API documentation.
Testing the Keywords locally
Before deploying anything, you can run the Keywords straight from your machine. The unit test in nodejs-keywords/tests/keywords-typescript.test.ts uses the step-node-agent runner to invoke them exactly as an agent would:
// The runner's first argument is the Keyword properties — the same channel
// Step feeds from its Parameters, with values as strings.
const keywordRunner = runner({ headless: 'true' });
const found = await keywordRunner.run('findProduct', {
url: 'https://opencart-prf.stepcloud.ch/',
productName: 'iMac',
});
assert.match(found.payload.price, /^\$\d+\.\d{2}$/);
await keywordRunner.run('checkout');
Run it with:
npm test
This gives you immediate feedback on your Keywords without a round-trip to a Step cluster — the fastest way to iterate while developing.
run() throws when a Keyword reports an error, so reaching the next line already proves it succeeded — no assertion needed. The price is matched as a pattern rather than a literal, so a catalogue change on the live store does not break the test. And keywordRunner.close() in the after hook disposes the session, which closes the browser opened by findProduct.
test script chains three things: npm run build, then npm run typecheck, then the tests. The type-check step exists because tsx strips types without verifying them — without tsconfig.test.json, the test sources would never be checked by the compiler.
Understanding the Automation Package descriptor
The automation-package.yaml file at the root of the sample tells Step what the package contains.
The Keywords are declared as Node Keywords, both pointing at the nodejs-keywords/ directory:
keywords:
- Node:
name: findProduct
jsfile: nodejs-keywords/
- Node:
name: checkout
jsfile: nodejs-keywords/
The technical configuration discussed above is declared as a package Parameter, which is how it reaches the Keywords as properties:
parameters:
# Set to "false" to watch the browser while the plan runs
- key: headless
value: "true"
Because it is a Parameter and not an input, the same knob can be redefined in Step — globally, or per project or environment — without touching a single Plan. Set it to "false" to watch the browser as the Plan runs; Step agents support both headed and headless execution.
The Plans then call the Keywords. The functional test case runs them once in sequence:
- name: "Functional Test Case - OpenCart purchase flow"
agents: auto_detect
root:
testSet:
children:
- testCase:
nodeName: "OpenCart Test Case 01"
children:
- callKeyword:
keyword: "findProduct"
inputs:
- url: "https://opencart-prf.stepcloud.ch/"
- productName: "iMac"
- callKeyword:
keyword: "checkout"
While the load test wraps the very same calls in a thread group and a session, so each simulated user runs the full journey in its own browser session:
- name: "Load Test - OpenCart purchase flow"
agents: auto_detect
root:
threadGroup:
users: 5
iterations: 10
children:
- session:
children:
- callKeyword:
keyword: "findProduct"
inputs:
- url: "https://opencart-prf.stepcloud.ch/"
- productName: "iMac"
- callKeyword:
keyword: "checkout"
For the full syntax, refer to the Automation Package descriptor documentation.
Building the project
Step executes JavaScript, not TypeScript, so the sources have to be compiled before the package is sent to your cluster:
cd step-samples/automation-packages/playwright-typescript/nodejs-keywords
npm run build
This runs tsc, compiling everything under src/ into the keywords/ directory — the location the jsfile property in the descriptor points to.
keywords/ is generated and not tracked in Git, so a fresh clone has no compiled output at all. If you deploy without building, or change a .ts file without rebuilding, the agent will run stale JavaScript — or fail to find any.
.apignore file controls what actually gets uploaded. src/, tests/, tsconfig.test.json and node_modules/ are all excluded: the agent only needs package.json — it runs npm install itself — and the compiled keywords/ directory. Keeping node_modules/ out is what keeps the uploaded package small.
Executing in Step
Before talking to your cluster, you need three things: its URL, the project to target, and an API key. For instructions on generating a key, refer to Generate an API Key.
From the root of the Automation Package, run:
cd step-samples/automation-packages/playwright-typescript
step ap execute --stepUrl=https://<Hostname of your Step cluster> --projectName=Common --token=<Your API key>
This packages the project, sends it to your Step cluster, and immediately runs the Plans it contains. Use it for a quick end-to-end verification from your machine or from a CI pipeline.
--projectName and --token are required on Step SaaS and on the Enterprise edition, which are multi-tenant and authenticated. On an open-source cluster you can omit both and pass --stepUrl alone. Treat the API key as a secret: in a CI pipeline, inject it from a secret store rather than writing it into the command.
Deploying to Step
Executing does not leave anything behind on the cluster. To make the package permanently available — so the Plans can be scheduled or launched from the Step UI — deploy it instead:
cd step-samples/automation-packages/playwright-typescript
step ap deploy --stepUrl=https://<Hostname of your Step cluster> --projectName=Common --token=<Your API key>
Once deployed, both the functional test case and the load test appear in the Step UI, ready to be executed, scheduled, or included in larger Plans — and the headless Parameter can be overridden there without changing the package.
Troubleshooting
- Build fails: make sure all dependencies were installed with
npm install - TypeScript compilation errors: check your
tsconfig.jsonconfiguration - Step CLI not found: verify the Step CLI is installed and available on your
PATH - Authentication or authorization errors from the CLI: on SaaS and Enterprise clusters, check that
--tokenholds a valid, unexpired API key and that--projectNamenames a project your user can access - Keyword not found at execution time: you most likely skipped
npm run build, or thejsfilepath inautomation-package.yamldoes not match theoutDirintsconfig.json - No agent available (on-premise clusters): Node.js Keywords require a Node agent registered on your cluster. On SaaS clusters this is handled for you.
- Runtime errors after switching the project to ESM: keep the project CommonJS. The Step Node agent copies CommonJS helpers into the Keyword project and forks them, so adding
"type": "module"topackage.jsonbreaks the runtime.tsconfig.jsonuses"module": "node20", which keeps the CommonJS emit while enabling modern module resolution.
Additional resources
This article demonstrates how to connect Grafana to data generated by Step.
This article demonstrates how to set up distributed system monitoring using Keyword executions, and analyze the results as measurements.
This tutorial demonstrates how to automate interaction with Microsoft Office applications using the Office Interop Assembly.
This article provides documentation for how to integrate JUnit tests into Step.
This tutorial demonstrates how Step can be used to monitor services, availability and performance metrics.
This tutorial demonstrates how to utilize the AutoIt C# binding to automate interactions with Windows applications.
This article demonstrates the automation of mobile applications on Android using the Appium framework.
This article defines three Keywords which will be used in browser-based automation scenarios, using Step and Selenium, as general drivers.
This tutorial shows you how to efficiently set up a browser-based load test using existing Cypress tests in the Step automation platform.
In this short tutorial, we show how to quickly implement a simple browser-based load test based on Cypress scripts in Step.
This tutorial shows you how to set up a browser-based load test using existing Playwright tests in the Step UI.
This article explains Keywords in Step and demonstrates how to create simple ones.
This tutorial demonstrates the design, execution, and analysis of functional tests using the web interface of Step.
This tutorial will demonstrate how to use Step and Selenium to automate various browser tasks.
This tutorial demonstrates how to use Step and Cypress to automate various browser tasks.
This tutorial demonstrates how Selenium automation tests can be turned into full synthetic monitoring using Step.
In this tutorial, you'll learn how to reuse existing Cypress tests to quickly set up and run a browser-based load test using the automation as code approach.
In this tutorial, you'll learn how to reuse existing tests written with Serenity BDD and Cucumber for load testing.
This tutorial demonstrates how Cypress automation tests can be turned into full synthetic monitoring using the automation as code approach.
In this tutorial, you'll learn how to reuse existing Cypress tests to quickly set up and run a browser-based load test using the Step UI.
This tutorial demonstrates how to leverage existing Selenium tests to set up and execute browser-based load tests, following a full code-based approach.
This tutorial demonstrates how to set up a browser-based load test in the Step UI using existing Selenium tests.
This tutorial demonstrates how Playwright automation tests can be turned into full synthetic monitoring using Step.
This tutorial demonstrates how Cypress automation tests can be turned into full synthetic monitoring using the Step UI.
This tutorial will demonstrate how to use Step and Playwright to automate various browser tasks.
This tutorial shows how to distribute JMeter tests across multiple nodes.
In this tutorial, you'll learn how to reuse existing Playwright tests written in Java to quickly set up and run a browser-based load test using the automation as code approach.
This tutorial demonstrates how Playwright tests can be reused for synthetic monitoring of a productive environment in a DevOps workflow
This tutorial shows how to distribute Grafana K6 tests across multiple nodes.
This tutorial demonstrates how Playwright tests can be reused for synthetic monitoring of a productive environment in a DevOps workflow
In this tutorial you'll learn how to quickly set up a protocol-based load test with okhttp
Learn how to set up continuous end-to-end testing across several applications based on Playwright tests in your DevOps pipeline using Step
Learn how to quickly set up continuous browser-based load testing using Playwright tests in your DevOps pipeline
End-to-end walkthrough: author a test, run it, and analyse the results using plain English prompts in Claude via the Step MCP Server.
Want to hear our latest updates about automation?
Don't miss out on our regular blog posts - Subscribe now!