PRACTICAL GUIDE / keyword driven vs data driven testing
Keyword Driven vs Data Driven Testing: Clear Guide
Compare keyword driven vs data driven testing with examples, tables, framework design tips, maintenance risks, and QA use cases for QA teams.
In this guide8 sections
What you will learn
- Separate the two design axes
- Build a data-driven test with typed rows
- Choose cases by risk, not by available rows
- Design keywords as a stable domain API
A regression team moved 240 login cases into a spreadsheet so analysts could maintain them without code changes. Six weeks later, a button rename broke every row because each test also stored the same click sequence. The team called the framework data driven, but the real source of duplication was workflow. Another group solved that problem with keywords, then created one keyword per test and merely moved the scripts into YAML.
The keyword driven vs data driven testing decision is not a choice between two competing tools. Data driving varies inputs and expected results for one behavior. Keyword driving composes behavior from a controlled vocabulary of actions. A suite can use either, both, or neither. The right design follows the kind of change the suite must absorb.
Separate the two design axes
Data-driven tests keep the procedure in code and supply rows from an array, file, database, or generator. One password policy test might run for empty, short, valid, and overlong values. The framework is responsible for parsing, validating, naming, and isolating each row.
Keyword-driven tests represent a procedure as named operations such as Create customer, Add item, and Verify total. An engine maps each keyword to implementation code. The vocabulary becomes an interface between test authors and automation maintainers.
| Question | Data driven | Keyword driven |
|---|---|---|
| What varies most? | Inputs and expected outputs | Action sequence |
| Main abstraction | Typed test case | Controlled operation |
| Typical authoring risk | Invalid or excessive rows | Ambiguous or low-level keywords |
| Failure should identify | Dataset and field | Step, arguments, and state |
| Best fit | Rules with meaningful partitions | Repeated workflows composed by non-coders |
Neither approach guarantees maintainability. Externalizing values can hide intent, and externalizing actions can create a programming language with poor tooling. Compare the ongoing authoring model, not the first demo.
The distinction also clarifies reviews. A changed data row should prompt questions about coverage and expected results. A changed keyword should prompt questions about every workflow that calls it. Treating both as ordinary spreadsheet edits hides their very different blast radii.
Build a data-driven test with typed rows
Keep nearby data in code when developers and automation engineers own it. Types, review diffs, and editor support are usually better than a spreadsheet. This Playwright example validates login outcomes without sharing a browser session between rows:
import { expect, test } from "@playwright/test";
type LoginCase = {
name: string;
email: string;
password: string;
outcome: "dashboard" | "invalid-credentials" | "locked-account";
};
const cases: LoginCase[] = [
{
name: "active customer",
email: "active@example.test",
password: "Valid-Password-7",
outcome: "dashboard"
},
{
name: "wrong password",
email: "active@example.test",
password: "wrong",
outcome: "invalid-credentials"
},
{
name: "locked customer",
email: "locked@example.test",
password: "Valid-Password-7",
outcome: "locked-account"
}
];
for (const row of cases) {
test(`login: ${row.name}`, async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill(row.email);
await page.getByLabel("Password").fill(row.password);
await page.getByRole("button", { name: "Sign in" }).click();
if (row.outcome === "dashboard") {
await expect(page).toHaveURL(/\/dashboard$/);
} else {
await expect(page.getByTestId("login-error")).toHaveAttribute(
"data-code",
row.outcome
);
}
});
}The row has a diagnostic name and a constrained outcome instead of an arbitrary expected message. UI copy can change without weakening the domain assertion. Each generated test gets normal setup, reporting, retries, and an independent result.
External files make sense when data has a different owner, changes on a different schedule, or is generated by another system. Add schema validation at load time. A CSV cell is always text, so false, an empty string, and a missing value can be confused unless parsing rules are explicit.
Choose cases by risk, not by available rows
Data-driven suites often grow through copying. Ten valid countries become two hundred combinations of countries, currencies, devices, and account types, even though most combinations exercise the same branch. Execution cost rises while defect detection barely changes.
Start from equivalence classes, boundaries, decision tables, and production risks. Give each row a reason. If a tax rule depends on country and customer category, cover the combinations that change the decision. Use pairwise generation only after defining constraints and reviewing what interactions matter. Generated data should include a seed or persisted failing case so a failure can be reproduced.
Keep secrets and personal data out of datasets. Reference a credential alias resolved by the test environment instead of storing a password in CSV. Synthetic records should still obey realistic formats and relationships. A test with an impossible postal code may pass validation paths that real data never reaches.
Verify a dataset by intentionally corrupting a required field, duplicating a case ID, and adding an unsupported expected outcome. Loading must fail before browser or API actions begin. Silent coercion is a framework defect because it can make a malformed case appear green.
Design keywords as a stable domain API
Good keywords describe capabilities, not individual clicks. Submit refund can change from three UI interactions to one API call while preserving its meaning. Click blue button exposes presentation details and cannot explain business intent.
A compact YAML workflow could look like this:
name: refund a captured card payment
steps:
- keyword: create_customer
args:
alias: buyer
- keyword: capture_payment
args:
customer: buyer
amount_minor: 2500
currency: INR
- keyword: request_refund
args:
payment: last_payment
amount_minor: 2500
- keyword: verify_payment_status
args:
payment: last_payment
expected: refundedThe vocabulary is intentionally small and domain focused. Aliases let later operations refer to created entities without exposing generated IDs to the author. Monetary units are explicit, which avoids decimal and currency ambiguity.
Treat keyword names and arguments as a versioned public interface. Define required fields, types, defaults, produced context values, and failure behavior. Reject unknown arguments. If misspelling amount_minor is ignored, the engine may use a default and report a dangerous false pass.
Implement a transparent keyword engine
The dispatcher should be boring. It validates the document, finds a registered operation, executes it with scenario-scoped context, and records structured evidence. Avoid eval, reflection over arbitrary method names, or code embedded in cells.
type Args = Record<string, unknown>;
type Context = {
values: Map<string, string>;
api: { post(path: string, body: unknown): Promise<Response> };
};
type Keyword = (args: Args, context: Context) => Promise<void>;
const keywords: Record<string, Keyword> = {
create_customer: async (args, context) => {
if (typeof args.alias !== "string") {
throw new Error("create_customer.alias must be a string");
}
const response = await context.api.post("/test-support/customers", {});
if (response.status !== 201) throw new Error(`customer setup returned ${response.status}`);
const body = await response.json() as { id: string };
context.values.set(args.alias, body.id);
},
verify_payment_status: async (args, context) => {
if (typeof args.expected !== "string" || typeof args.payment !== "string") {
throw new Error("verify_payment_status requires payment and expected");
}
const actual = context.values.get(`${args.payment}:status`);
if (actual !== args.expected) {
throw new Error(`expected payment status ${args.expected}, received ${actual}`);
}
}
};
export async function runStep(
step: { keyword: string; args?: Args },
context: Context
): Promise<void> {
const operation = keywords[step.keyword];
if (!operation) throw new Error(`unknown keyword: ${step.keyword}`);
await operation(step.args ?? {}, context);
}In a real engine, validate the complete YAML against a schema before execution and add timing, attachments, and redaction around runStep. Report the workflow name, step index, keyword, sanitized arguments, and underlying cause. Authors should not need to search runner logs to learn which operation failed.
Keyword frameworks fail when they reproduce a general-purpose language badly. Conditions, loops, variables, imports, and exception handling quickly create programs without a compiler or debugger. Keep branching inside tested keyword implementations. When authors need complex control flow, ordinary test code is the more honest tool.
Combine approaches only where each adds value
A hybrid framework can run the same workflow against several meaningful datasets. For example, a refund workflow may use card and wallet rows with different settlement expectations. The keyword sequence stays stable while provider, currency, amount, and expected status vary.
Do not build a Cartesian product automatically. A workflow with twelve steps and fifty rows produces six hundred operation executions, often through a slow UI. Decide which variations need the full workflow and which belong in API or unit tests. One UI case can prove wiring while lower layers cover calculation boundaries.
Keep workflow definitions and datasets independently identifiable. A result named refund.yaml / wallet-delayed-settlement is actionable. row 18 failed at step 9 is not. Reports should record the keyword library version and dataset revision so reruns use the same inputs and behavior.
Hybrid designs also need ownership boundaries. Domain analysts may propose rows and review workflows, but automation maintainers should own keyword implementation, schemas, and runner releases. Without that separation, an apparently harmless table edit can alter execution semantics.
Decide using repository evidence
Choose data driving when one procedure is stable and defects cluster around input partitions, boundaries, locales, roles, or expected results. Choose keyword driving when a genuine non-coding author group needs to compose a limited set of recurring domain workflows and the organization can maintain the vocabulary as a product.
Stay with direct test code when engineers own the suite, workflows are unique, refactoring support matters, or the proposed keyword layer merely renames library calls. A page object or API client already provides reuse without adding a separate file format.
Run a pilot with five representative tests, including setup failure and a product defect. Measure review clarity, time to diagnose, required code changes, and whether intended authors can safely modify cases. Also make a UI or API change that should be absorbed by one abstraction. The location and size of the repair reveal whether the design actually isolates change.
Govern the suite after adoption
For data-driven tests, review row purpose, schema validity, uniqueness, sensitive fields, and execution growth. Remove redundant cases when the same rule is covered more cheaply elsewhere. Assign stable case IDs if results feed release reporting.
For keywords, maintain a searchable catalog with argument contracts and examples. Deprecate operations before removing them, find all workflow references in CI, and prevent synonyms from multiplying. A pull request that adds check_order, verify_order, and assert_order should trigger a vocabulary discussion.
Both styles need isolated state, deterministic cleanup, useful artifacts, and clear failure ownership. Neither should conceal flaky waits or environment coupling behind a friendly format. Test the framework itself: parser errors, invalid documents, duplicate IDs, missing keywords, cleanup failure, secret redaction, and parallel execution all deserve automated checks.
The strongest comparison is a maintenance exercise. Change a locator, add a required API field, revise a business rule, and inspect how many artifacts must change. If data varies without duplicating procedure, the data-driven layer is earning its cost. If workflows remain readable while keyword internals absorb technical change, the keyword layer is doing its job. If both changes ripple through spreadsheets and engine code, simplify the architecture.
// FIELD DISPATCH
Get the QA Field Notes
Weekly QA battles, AI testing guides, and interview drills. Free on Substack.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01
FAQ / QUICK ANSWERS
Questions testers ask
When should a suite be data driven instead of keyword driven?
Choose data driving when the procedure is stable and meaningful variation lies in inputs, boundaries, roles, locales, or expected outcomes. Choose keyword driving only when a real non-coding author group must compose recurring workflows from a limited domain vocabulary. If engineers own unique flows, direct test code is usually simpler.
When does an external spreadsheet improve data-driven testing?
Use an external file when the data has a separate owner, release cadence, generator, or sharing requirement. Otherwise, typed data beside the test usually gives better review and editor support. Validate the schema before execution, reject duplicate IDs and unsupported outcomes, and distinguish missing, empty, and false values explicitly.
What makes a keyword maintainable rather than another scripted click?
A durable keyword names a domain capability such as submitting a refund, not an interface action such as clicking a colored button. Give it typed arguments, defined outputs, failure behavior, one owner, and adapter tests. Reject unknown arguments so a misspelling cannot silently invoke a default and create a false pass.
Can keyword-driven workflows also use multiple datasets safely?
Yes, when each technique varies a different axis. Keep the workflow stable while selected rows vary provider, currency, amount, or expected settlement state. Avoid automatic Cartesian products, identify both workflow and dataset in results, and move broad calculation combinations to faster API or unit coverage instead of repeating the full UI flow.
What should a pilot reveal before either framework style is adopted?
Run several representative cases that include setup failure and a genuine product defect. Then change a locator, required API field, and business rule. Measure review clarity, diagnosis time, author safety, and how many artifacts each repair touches. A design earns adoption only when the intended abstraction absorbs its corresponding kind of change.
RELATED GUIDES
Continue the learning route
GUIDE 01
Test Automation Framework Types: Complete QA Guide
Explore test automation framework types, including modular, data driven, keyword driven, hybrid, BDD, page object, and CI-ready frameworks now.
GUIDE 02
Data-Driven Testing in Selenium
Learn data-driven testing in Selenium using CSV, Excel, and TestNG DataProvider patterns to run one script across many reusable test datasets.
GUIDE 03
How to Build a Test Automation Framework from Scratch
Learn how to build a test automation framework from scratch with layers, design patterns, reporting, CI/CD hooks, and a practical starter architecture.
GUIDE 04
Page Object Model: A Pattern for Maintainable Tests
Learn the page object model for maintainable UI automation tests, with Playwright examples, best practices, and common POM mistakes to avoid.