egain-playwright-transformer
v0.1.1
Published
Transform recorded Playwright tests into data-driven test script
Downloads
258
Maintainers
Readme
🎭 Playwright Transformer
Playwright Transformer is an open-source utility that helps teams turn recorded Playwright UI tests into clean, maintainable, and data-driven automation. It’s built for developers and test engineers who want to move quickly without accumulating brittle or hard-to-maintain test code.
Instead of hard-coding values directly into test scripts, Playwright Transformer extracts test data, externalizes it into JSON files, and refactors tests to follow data-driven patterns. This keeps test logic focused on behavior, makes updates easier when UI or data changes, and helps test suites scale as applications grow.
Whether you’re experimenting with Playwright for the first time or maintaining a large automation suite, Playwright Transformer aims to reduce duplication, improve readability, and make UI tests easier to evolve over time.
✨ Features
Automated Value Extraction • Pattern-Based Transformation
Smart Pattern Matching. Automatically identifies and extracts values from Playwright actions like
.fill(),.selectOption(),.click(), and more using configurable regex patterns and externalize these into data files.Data-Driven Conversion. Transforms hardcoded test values into data references, enabling easy test data management and parameterization. Generated external data file is json array which enables test data addition.
Flexible Configuration. Uses JSON-based configuration files for patterns, replacements, and transformations—no code changes needed to customize behavior.
Pre-processing Pipeline. Automatically inserts boilerplate code, removes noise lines, and applies custom transformations before pattern processing.
📋 Table of Contents
- Features
- Installation
- Quick Start
- Usage
- How It Works
- Configuration
- Examples
- API Reference
- Development
- Contributing
- License
- Related Projects
- Support
🚀 Installation
Prerequisites
- Node.js >= 18.0.0 (required for building the package)
- npm or yarn
Install from npm
npm install -D egain-playwright-transformerOr using yarn:
yarn add -D egain-playwright-transformerAfter installation, you can use the CLI directly via below command:
node node_modules/egain-playwright-transformer/dist/cli.mjs --all \
--input-dir tests/input \
--output-dir tests/output \
--data-dir data/outputInstall from Source
If you've cloned or forked this repository and want to use it directly from source:
Clone the repository:
git clone https://github.com/egain/playwright-transformer.git cd playwright-transformerInstall dependencies:
npm installBuild the project:
npm run buildUse the CLI from the built files:
node dist/cli.mjs --all \ --input-dir tests/input \ --output-dir tests/output \ --data-dir data/output
Note: You'll need to rebuild (
npm run build) after making any changes to the source code.
🎯 Quick Start
Prerequisites
- Make sure application under test uses static ids - preferably follow best practise to use data-testid.
- Before running the transformer, ensure you have a
config/directory in your project root with all required configuration files:
fill_patterns.jsonreplace_texts.jsoninsert_lines.jsonprepend.tstest_start.ts
The transformer will auto-detect the config/ directory relative to your current working directory.
Using the CLI
The easiest way to get started is using the command-line interface as below where:
--input-dir= folder with recorded test scripts--output-dir= folder where transformed script will be generated--data-dir= folder where output data files will be generated
When installed as npm package:
# Transform all test files
node node_modules/egain-playwright-transformer/dist/cli.mjs \
--all \
--input-dir tests/input \
--output-dir tests/output \
--data-dir data/outputWhen using from source (cloned/forked repo):
# First, make sure you've built the project (npm run build)
# Then run:
node dist/cli.mjs \
--all \
--input-dir tests/input \
--output-dir tests/output \
--data-dir data/outputUsing npm scripts
Add to your package.json:
{
"scripts": {
"transform:all": "node node_modules/egain-playwright-transformer/dist/cli.mjs --all --input-dir tests/input --output-dir tests/output --data-dir tests/data"
}
}Then run:
# Transform all files
npm run transform:all
📖 Usage
CLI Usage
The CLI provides a simple interface for transforming test files:
When installed as npm package:
# Transform all test files
node node_modules/egain-playwright-transformer/dist/cli.mjs --all \
--input-dir tests/input \
--output-dir tests/output \
--data-dir data/output
When using from source (cloned/forked repo):
# Make sure you've built the project first (npm run build)
node dist/cli.mjs \
--all \
--input-dir tests/input \
--output-dir tests/output \
--data-dir data/outputOptions
| Option | Short | Description | Required |
| -------------- | ----- | ----------------------------------------------------------------- | -------- |
| --input-dir | -i | Directory containing source test files (.spec.ts) | ✅ Yes |
| --output-dir | -o | Directory for transformed test files | ✅ Yes |
| --data-dir | -d | Directory for JSON data files | ✅ Yes |
| --all | -a | Transform all files in input directory (default: first file only) | ❌ No |
| --help | -h | Show help message | ❌ No |
Note: By default (without
--allflag), the transformer processes only the first test file found in the input directory. Use the--allflag to transform all test files.
Examples
# Transform all test files
node node_modules/egain-playwright-transformer/dist/cli.mjs --all \
--input-dir ./tests/input \
--output-dir ./tests/output \
--data-dir ./data/outputProgrammatic API
For more control, use the programmatic API:
import { transform, PlaywrightTransformer } from 'egain-playwright-transformer';
// Advanced usage - transform all files
const transformer = new PlaywrightTransformer({
inputDir: './tests/input',
outputDir: './tests/output',
dataDir: './data/output',
});
// Transform all files in the input directory
const result = await transformer.transformAll();
if (result.success) {
console.log(`Transformed ${result.transformedFiles} files`);
} else {
console.error('Errors:', result.errors);
}Note: The
transform()function andtransformer.transform()method process only the first test file found in the input directory. Usetransformer.transformAll()to process all files.
🔧 How It Works
Playwright Transformer follows a multi-phase transformation pipeline:
1. Pre-processing Phase
- Apply Insert Line Patterns: Processes patterns from
insert_lines.jsonto insert, update, or remove lines - Remove Noise: Filters out unnecessary lines like goto steps for browser redirect based on skip patterns
- Prepend Boilerplate: Injects import statements and setup code from
prepend.tsat the beginning of each test file
2. Pattern Processing Phase
For each line in the test file:
Pattern Matching: Analyzes the line against configured patterns
Value Extraction: Identifies hardcoded values in actions like:
.fill('value')→ extracts'value'.selectOption('option')→ extracts'option'- And all other pattern types in
fill_patterns.json
Data Mapping: Stores extracted values in maps:
jsonMap: Maps field names to values (for JSON output)reverseJsonMap: Maps values to data references (for replacement)dynamicIdsMap: Tracks dynamic selectors
3. Transformation Phase
Fill Pattern Handler: Replaces hardcoded values with data references. The
uniqueIndexis automatically appended to values unless the key is specified innonUniqueKeysorkeysToBeTreatedAsConstants:// Before await page.getByTestId('username').fill('[email protected]'); await page.getByTestId('email').fill('[email protected]'); // email in nonUniqueKeys // After (if username is not in nonUniqueKeys) await page.getByTestId('username').fill(data.username + uniqueIndex); await page.getByTestId('email').fill(data.email); // no uniqueIndex for emailTestCase Start Handler: When a
test()line is encountered, it usestest_start.tsas a template, replacing placeholders like[[TEST_CASE_NAME_MATCHER]]with the actual test name and data referencesDefault Pattern Handler: Applies text replacements from
replace_texts.json:- Data value replacement in locators and selectors
- Skip constant value substitution
- Exclude unique index addition for keys defined in
keysToBeTreatedAsConstants
Special Handlers: Handle test structure, file endings, complete file name placeholders, and special cases
4. Output Generation Phase
- Transformed Test File: Writes the data-driven test to the output directory
- JSON Data File: Generates a JSON file with all extracted values:
{ "tcName": "TC01", "username": "[email protected]", "password": "secret123" }
Transformation Example
Input (input/TC01_Login.spec.ts):
import { test } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByTestId('username').fill('[email protected]');
await page.getByTestId('password').fill('secret123');
await page.getByRole('button', { name: 'Sign in' }).click();
});Output (output/TC01_Login.spec.ts):
import { test } from '@playwright/test';
import input from '@data/output/TC01_Login.json';
for (const data of input) {
test(`${data['tcName']}`, async ({ page }) => {
await page.goto(process.env.BASE_URL);
await page.getByTestId('username').fill(data.username);
await page.getByTestId('password').fill(data.password);
await page.getByRole('button', { name: 'Sign in' }).click();
});
}Data File (data/output/TC01_Login.json):
[
{
"tcName": "TC01",
"username": "[email protected]",
"password": "secret123"
}
]⚙️ Configuration
Playwright Transformer uses JSON configuration files located in the config/ directory. The config/ directory must exist in your project root (where you run the CLI command) with all required configuration files as mentioned below. Refer to configuration files used in different examples in example folder.
Configuration Files
| File | Purpose | Description |
| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
| fill_patterns.json | Value Extraction | Defined regex patterns for extracting test data values from recorded script steps. |
| replace_texts.json | Text Replacement | Rules for replacing hardcoded data values in recorded scripts with data references from externalized data |
| insert_lines.json | Code Injection | Inserts/Updates steps based on defined "existingLines" in json |
| prepend.ts | Boilerplate | Define all import statements code needed for tests |
| test_start.ts | Boilerplate | Define initial steps of test case include any custom method initialization if needed |
Customizing Patterns
Edit the JSON configuration files to customize transformation behavior:
1. fill_patterns.json example
Below patterns will get data from all steps which has
- "getByTestId()" and "fill()"
- "getByTestId()" and "selectOption()" to parameterize the value in fill() and selectOption() in external data file. This will create data file json with data for username and password when transformer is run.
[
{
"regex": "getByTestId\\(['\"]([^'\"]+)['\"]\\)\\.fill\\(['\"]([^'\"]+)['\"]\\)",
"groupNoForKey": 1, //group 1 from above regex will be used as key in data file
"groupNoForValue": 2, //group 2 from above regex will be used as value in data file
"keysToBeTreatedAsConstants": "", //comma separated values of testids which should be parameterized
"nonUniqueKeys": "email", //this ensures that unique index is NOT added for step with this key.
"isKeyFrameset": "false",
"isContentFrameHandlingNeeded": "false", //set this to true if you want fill() to replaced with pressSequentially() to simulate typing of content
"ignorePathInValue": "false", //this will ignore the path of attachment file and only parameterize file name.
"isFileUpload": "false" //this is set to true for steps which has file upload.
},
{
"regex": "(?=.*getByTestId\\('([^']*)'\\))(?=.*selectOption\\('(.*)'\\)).*",
"groupNoForKey": "1",
"groupNoForValue": "2",
"keysToBeTreatedAsConstants": "",
"nonUniqueKeys": "country",
"isKeyFrameset": "false",
"isContentFrameHandlingNeeded": "false",
"ignorePathInValue": "false",
"isFileUpload": "false"
}
]2. replace_texts.json example
Below will update the value in all the steps which have texts prepended ".getByTestId(" and appended by "')" to use the data from the json data file thereby parameterizing the data.
[
{
"dataPrependedBy": ".getByTestId('",
"dataAppendedBy": "')",
"isWholeWordMatch": "false",
"removeDateAtTheEnd": "true", //if there is timestamp in the end - it will be removed/kept based on value of this field
"keysToBeTreatedAsConstants": "email", //this will not append unique index to step wherever key "email" is used from data file in the the test script
"replaceWithDynamicIds": "false",
"keysForFileUploading": "",
"valuesToBeIgnored": "0,1" //values defined in this will not be parameterized
}
]3. insert_lines.json example
Configure this file to include existing patterns which needs to be transformed with different steps. In addition to below patterns, add any other transformation which is required for any step to below list of transformation.
[
//this will remove the import statement mentioned below
{
"existingLines": "import { test, expect } from '@playwright/test'",
"removeLines": "0",
"separator": "\\|"
},
{
//This will remove all redirects in recorded script, pattern can be updated to exclude any specific goto which need not be removed.
"existingLines": ".goto\\('[^']*'\\)",
"isRegex": "true",
"removeLines": "0"
}
]4. prepend.ts example
In addition to below values include any other imports which are needed for your tests
import { expect, test } from '@playwright/test';
import input from '@[[DATA_SOURCE_PATH_PLACEHOLDER]]/[[COMPLETE_TEST_FILE_NAME]].json' assert { type: 'json' };
import { faker } from '@faker-js/faker';
import path from 'path';5. test_start.ts example
This file serves as a template for the test case structure. When the transformer encounters a test() line in the input file, it uses this template and replaces placeholders:
[[TEST_CASE_NAME_MATCHER]]- Replaced with the original test name plus${data['tcName']}- Other placeholders are replaced during transformation
Include any additional steps needed in your tests before goto() steps. This is useful when you want to initialize any class, set any variables in your tests at start - for using it later in transformation in insert_lines.json file.
for (const data of input) {
test(`[[TEST_CASE_NAME_MATCHER]]`, async ({ page }) => {
const uniqueIndex = "_" + faker.string.alphanumeric(10) //this unique index will be used in all test data, can be updated to empty string if not needed to be used.
await page.goto(process.env.BASE_URL); //This ensures that browser is launched using the BASE_URL defined in env file📚 Examples
Example 1: Basic Form Fill
Input:
await page.getByTestId('email').fill('[email protected]');
await page.getByTestId('name').fill('John Doe');fill_patterns.json:
[
{
"regex": "getByTestId\\(['\"]([^'\"]+)['\"]\\)\\.fill\\(['\"]([^'\"]+)['\"]\\)",
"groupNoForKey": 1,
"groupNoForValue": 2,
"keysToBeTreatedAsConstants": "",
"nonUniqueKeys": "email",
"isKeyFrameset": "false",
"isContentFrameHandlingNeeded": "false",
"ignorePathInValue": "false",
"isFileUpload": "false"
}
]replace_texts.json:
[
{
"dataPrependedBy": ".getByTestId('",
"dataAppendedBy": "')",
"isWholeWordMatch": "false",
"removeDateAtTheEnd": "true", //if there is timestamp in the end - it will be removed/kept based on value of this field
"keysToBeTreatedAsConstants": "email", //this will not append unique index to step wherever key "email" is used from data file in the the test script
"keysForFileUploading": "",
"valuesToBeIgnored": "0,1" //values defined in this will not be parameterized
}
]Transformed:
await page.getByTestId('email').fill(data.email);
await page.getByTestId('name').fill(data.name + uniqueIndex);JSON Output:
[
{
"tcName": "TC01",
"email": "[email protected]",
"name": "John Doe"
}
]Example 2: Dropdown Selection
Input:
await page.getByTestId('country').selectOption('United States');fill_patterns.json:
[
{
"regex": "(?=.*getByTestId\\('([^']*)'\\))(?=.*selectOption\\('(.*)'\\)).*",
"groupNoForKey": "1",
"groupNoForValue": "2",
"keysToBeTreatedAsConstants": "",
"nonUniqueKeys": "country", //this will not append unique index to step with this key
"isKeyFrameset": "false",
"isContentFrameHandlingNeeded": "false",
"ignorePathInValue": "false",
"isFileUpload": "false"
}
]replace_texts.json:
[
{
"dataPrependedBy": ".selectOption('",
"dataAppendedBy": "')",
"isWholeWordMatch": "false",
"removeDateAtTheEnd": "true", //if there is timestamp in the end - it will be removed/kept based on value of this field
"keysToBeTreatedAsConstants": "country", //this will not append unique index to step with this key
"keysForFileUploading": "",
"valuesToBeIgnored": "0,1" //values defined in this will not be parameterized
}
]Transformed:
await page.getByTestId('country').selectOption(data.country);JSON Output:
[
{
"tcName": "TC01",
"country": "United States"
}
]Example 3: File Upload
Input:
await page.getByTestId('upload').setInputFiles('path/to/file.pdf');fill_patterns.json:
[
{
"regex": "(?=.*getByTestId\\('([^']*)'\\))(?=.*setInputFiles\\('(.*)'\\)).*",
"groupNoForKey": "1",
"groupNoForValue": "2",
"keysToBeTreatedAsConstants": "", //comma separated test-dataid to be added if transformation not needed for any element with this id.
"nonUniqueKeys": "",
"isKeyFrameset": "false",
"isContentFrameHandlingNeeded": "false",
"ignorePathInValue": "true", //if this is set to true the relative path to the file will be ignored in test data and attachment file should be kept in attachment folder in root directory
"isFileUpload": "true" //this should be true as setInputFiles is used for fileupload step
}
]Transformed:
const filePath_1 = path.join(process.cwd(), 'attachments', data.upload);
await page.locator('//input[@type="file"]').setInputFiles(filePath_1);JSON Output:
[
{
"tcName": "TC01",
"upload": "file.pdf"
}
]📖 API Reference
transform(config: TransformerConfig): Promise<TransformResult>
Transforms the first test file found in the input directory. This is a convenience function that internally calls transformer.transform().
Parameters:
config.inputDir(string): Directory containing source test filesconfig.outputDir(string): Directory for transformed test filesconfig.dataDir(string): Directory for JSON data files
Returns: Promise<TransformResult>
Example:
// Transforms only the first file
const result = await transform({
inputDir: './tests/input',
outputDir: './tests/output',
dataDir: './data/output',
});Note: To transform all files, use
PlaywrightTransformer.transformAll()instead.
PlaywrightTransformer
Main transformer class for advanced usage.
Constructor
new PlaywrightTransformer(config: TransformerConfig)Methods
transform(): Promise<TransformResult>
Transforms the first test file found in the input directory.
Returns: Promise<TransformResult>
transformAll(): Promise<TransformResult>
Transforms all test files in the input directory.
Returns: Promise<TransformResult>
interface TransformResult {
success: boolean;
transformedFiles: number;
errors: string[];
}Types
interface TransformerConfig {
inputDir: string;
outputDir: string;
dataDir: string;
patternsFile?: string;
constantsFile?: string;
prependFile?: string;
}
interface TransformResult {
success: boolean;
transformedFiles: number;
errors: string[];
}🛠️ Development
Building
npm run buildRunning Tests
npm testDevelopment Mode
npm run dev🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🔗 Related Projects
- Playwright - End-to-end testing framework
- Playwright Test - Playwright's test runner
💬 Support
For issues, questions, or contributions, please open an issue on GitHub.
Made with ❤️ by the eGain Team
