playwright-healing-locators 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ name: Playwright Tests
2
+ on:
3
+ push:
4
+ branches: [ main, master ]
5
+ pull_request:
6
+ branches: [ main, master ]
7
+ jobs:
8
+ test:
9
+ timeout-minutes: 60
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v5
13
+ - uses: actions/setup-node@v5
14
+ with:
15
+ node-version: lts/*
16
+ - name: Install dependencies
17
+ run: npm ci
18
+ - name: Install Playwright Browsers
19
+ run: npx playwright install --with-deps
20
+ - name: Run Playwright tests
21
+ run: npx playwright test
22
+ - uses: actions/upload-artifact@v5
23
+ if: ${{ !cancelled() }}
24
+ with:
25
+ name: playwright-report
26
+ path: playwright-report/
27
+ retention-days: 30
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # 🎭 Playwright Healing Locators
2
+
3
+ A robust, easy-to-use helper library for Playwright that improves test stability by automatically resolving broken selectors.
4
+
5
+ When web UI structure changes are frequent (class renaming, attribute updates, dynamic ids), normal selectors break tests. This package helps by:
6
+
7
+ - ✅ Trying a second locator when primary fails (`regularHeal`)
8
+ - 🤖 Detecting similar elements based on selector keywords (`autoHeal`)
9
+ - 🎯 Supporting common locator methods (CSS, XPath, text, ARIA, role)
10
+ - 👀 Only continuing once a visible element is found, reducing false positives
11
+
12
+ ## 🔍 The Problem & Solution
13
+
14
+ ### ⚠️ Common Problem: Brittle selectors break tests
15
+ - UI changes happen all the time (CSS classes change, DOM structure shifts, IDs get regenerated, attributes are refactored).
16
+ - Traditional Playwright selectors (`page.locator('#x')`, `getByRole...`) fail immediately if the exact path is gone.
17
+ - Teams spend time constantly updating tests, creating flakiness and maintenance debt.
18
+
19
+ ### 💡 Why This Package is Needed
20
+ - It reduces **fast failure** when the first selector is invalid.
21
+ - It avoids **unnecessary rework** by attempting recovery first.
22
+ - It gives a **safe second chance** to selectors without manual intervention.
23
+
24
+ ### 🎉 What We Improved After Using This Package
25
+ - ✨ **Resilient tests**: a bad primary selector can change to fallback instead of failing test.
26
+ - 🔧 **Less maintenance**: one failing selector does not require immediate test patching.
27
+ - 🛡️ **Better coverage**: both normal (`regularHeal`) and self-healing (`autoHeal`) paths are protected.
28
+ - 🔐 **Higher confidence**: tests now verify actual element exists before action (`waitFor visible`).
29
+ - 📊 **Faster debugging**: healing logs show which attempt worked.
30
+
31
+ ## ⭐ Features
32
+
33
+ - `regularHeal`: manual fallback locators (primary + fallback array)
34
+ - `autoHeal`: automatic healing by keyword matching and candidate scoring
35
+ - Supports CSS, XPath, text, ARIA label, and role-based selectors
36
+ - Built-in retry with visibility wait
37
+
38
+ ## 📦 Installation
39
+
40
+ ```bash
41
+ npm install playwright-healing-locators
42
+ ```
43
+
44
+ ## 🚀 Usage
45
+
46
+ ```js
47
+ import { test, expect } from '@playwright/test';
48
+ import { regularHeal, autoHeal } from 'playwright-healing-locators';
49
+
50
+ test('Regular Healing with fallback', async ({ page }) => {
51
+ await page.goto('https://practicetestautomation.com/practice-test-login/');
52
+
53
+ await regularHeal.fill(page, {
54
+ primary: '#wrong-username',
55
+ fallbacks: [
56
+ { type: 'role', value: 'textbox', name: 'Username' }
57
+ ]
58
+ }, 'student');
59
+
60
+ await regularHeal.fill(page, {
61
+ primary: '#wrong-password',
62
+ fallbacks: [
63
+ { type: 'role', value: 'textbox', name: 'Password' }
64
+ ]
65
+ }, 'Password123');
66
+
67
+ await regularHeal.click(page, {
68
+ primary: '#wrong-submit',
69
+ fallbacks: [
70
+ { type: 'role', value: 'button', name: 'Submit' }
71
+ ]
72
+ });
73
+
74
+ await expect(page.getByText('Logged In Successfully')).toBeVisible();
75
+ });
76
+ ```
77
+
78
+ ```js
79
+ test('Auto Healing with keyword-based candidate search', async ({ page }) => {
80
+ await page.goto('https://practicetestautomation.com/practice-test-login/');
81
+
82
+ await autoHeal.fill(page, {
83
+ primary: '#username-field-invalid'
84
+ }, 'student');
85
+
86
+ await autoHeal.fill(page, {
87
+ primary: '#password-field-invalid'
88
+ }, 'Password123');
89
+
90
+ await autoHeal.click(page, {
91
+ primary: '#submit-btn-invalid'
92
+ });
93
+
94
+ await expect(page.getByText('Logged In Successfully')).toBeVisible();
95
+ });
96
+ ```
97
+
98
+ ## 📚 API
99
+
100
+ ### ✍️ `regularHeal.fill(page, options, value)`
101
+ - `page`: Playwright page
102
+ - `options.primary`: CSS/XPath primary selector
103
+ - `options.fallbacks`: Array of fallback { type, value, name? }
104
+ - `value`: text to fill
105
+
106
+ ### 🖱️ `regularHeal.click(page, options)`
107
+ - `page`: Playwright page
108
+ - `options.primary`: CSS/XPath primary selector
109
+ - `options.fallbacks`: Array of fallback { type, value, name? }
110
+
111
+ ### ✍️ `autoHeal.fill(page, options, value)`
112
+ - `page`: Playwright page
113
+ - `options.primary`: primary selector to auto-heal from
114
+ - `value`: text to fill
115
+
116
+ ### 🖱️ `autoHeal.click(page, options)`
117
+ - `page`: Playwright page
118
+ - `options.primary`: primary selector to auto-heal from
119
+
120
+ ## 🤝 Contribution
121
+
122
+ 1. 🍴 Fork repository
123
+ 2. 🌿 Create feature branch
124
+ 3. ✅ Add tests in `tests/*.spec.ts`
125
+ 4. 📤 Submit PR
126
+
127
+ ## ⚠️ Limitations & Known Issues
128
+
129
+ While this package improves test resilience, be aware of these considerations:
130
+
131
+ ### ⏱️ Performance Impact
132
+ - `autoHeal` searches the DOM for candidate elements, which may be slower on large/complex pages
133
+ - Multiple fallback attempts in `regularHeal` add execution time
134
+ - Consider using `regularHeal` for known stable fallbacks vs `autoHeal` for unpredictable UIs
135
+
136
+ ### 🎯 Accuracy Risks
137
+ - `autoHeal` keyword matching might select incorrect elements if multiple similar elements exist
138
+ - Always verify healing results in test reports and adjust primary selectors when possible
139
+ - False positives possible if element attributes match but context differs
140
+
141
+ ### 🔄 Maintenance Overhead
142
+ - `regularHeal` requires manual fallback definitions that need updates when UI changes
143
+ - Auto-healing might mask underlying selector problems that should be fixed
144
+ - Regular review of healing logs recommended to identify patterns
145
+
146
+ ### 🐛 Debugging Challenges
147
+ - When healing fails completely, error messages may not pinpoint the exact issue
148
+ - Healing logs help but require interpretation
149
+ - Complex DOM structures may confuse keyword extraction
150
+
151
+ ### 🌐 Browser Compatibility
152
+ - Tested on Chromium, Firefox, and WebKit as configured
153
+ - XPath support may vary slightly between browsers
154
+ - Mobile testing not yet validated
155
+
156
+ ### 📦 Dependencies
157
+ - Requires Playwright `^1.59.1` (may work with newer but not guaranteed)
158
+ - ES modules required (not CommonJS compatible)
159
+
160
+ ## 💻 Best Practices
161
+
162
+ - Use `regularHeal` for predictable, stable fallbacks
163
+ - Use `autoHeal` for dynamic content or when exact selectors are unreliable
164
+ - Monitor healing logs to identify frequently failing selectors
165
+ - Combine with good locator strategies (prefer roles/ARIA over fragile CSS)
166
+ - Don't rely solely on healing - fix underlying UI issues when possible
167
+
168
+ ## 📝 Notes
169
+
170
+ - Use a stable version of Playwright `^1.59.1`.
171
+ - Ensure `playwright.config.js` uses `reporter: 'html'` if you need HTML test reports.
172
+
173
+ ## 📄 License
174
+
175
+ This project is released under the **ISC License** - a permissive open-source license suitable for commercial and open-source projects.
176
+
177
+ See the [LICENSE](LICENSE) file for details.
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "playwright-healing-locators",
3
+ "version": "1.0.0",
4
+ "description": "A robust helper library for Playwright that improves test stability by automatically resolving broken selectors through fallback strategies and auto-healing.",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "npx playwright test",
9
+ "test:headed": "npx playwright test --headed",
10
+ "report": "npx playwright show-report"
11
+ },
12
+ "keywords": [
13
+ "playwright",
14
+ "testing",
15
+ "automation",
16
+ "selectors",
17
+ "healing",
18
+ "resilient",
19
+ "fallback",
20
+ "auto-heal",
21
+ "test-stability",
22
+ "e2e-testing"
23
+ ],
24
+ "author": "Iniyavan",
25
+ "license": "ISC",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/iniyavans/playwright-healing-locators.git"
29
+ },
30
+ "homepage": "https://github.com/iniyavans/playwright-healing-locators#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/iniyavans/playwright-healing-locators/issues"
33
+ },
34
+ "engines": {
35
+ "node": ">=16.0.0"
36
+ },
37
+ "peerDependencies": {
38
+ "@playwright/test": "^1.59.0"
39
+ },
40
+ "dependencies": {
41
+ "playwright": "^1.59.1"
42
+ },
43
+ "devDependencies": {
44
+ "@playwright/test": "^1.59.1",
45
+ "@types/node": "^25.5.0",
46
+ "playwright": "^1.59.1"
47
+ }
48
+ }
@@ -0,0 +1,102 @@
1
+ // TypeScript type checking for this config file
2
+ // @ts-check
3
+
4
+ // Import Playwright's configuration helper and device presets
5
+ import { defineConfig, devices } from '@playwright/test';
6
+
7
+ /**
8
+ * Optional: Read environment variables from a .env file.
9
+ * Uncomment the lines below if you want to use environment variables.
10
+ * https://github.com/motdotla/dotenv
11
+ */
12
+ // import dotenv from 'dotenv';
13
+ // import path from 'path';
14
+ // dotenv.config({ path: path.resolve(__dirname, '.env') });
15
+
16
+ /**
17
+ * Playwright test configuration
18
+ * This file configures how Playwright runs your tests.
19
+ * @see https://playwright.dev/docs/test-configuration
20
+ */
21
+ export default defineConfig({
22
+ // Directory where test files are located
23
+ testDir: './tests',
24
+
25
+ /* Run tests in files in parallel for faster execution */
26
+ fullyParallel: true,
27
+
28
+ /* Fail the build on CI if you accidentally left test.only in the source code.
29
+ This prevents focused tests from being committed to CI. */
30
+ forbidOnly: !!process.env.CI,
31
+
32
+ /* Retry failed tests: only retry on CI, not locally */
33
+ retries: process.env.CI ? 2 : 0,
34
+
35
+ /* Limit workers on CI to avoid resource conflicts.
36
+ Locally, use undefined to let Playwright decide. */
37
+ workers: process.env.CI ? 1 : undefined,
38
+
39
+ /* Reporter to use: 'html' generates a visual report */
40
+ reporter: 'html',
41
+
42
+ /* Shared settings for all test projects */
43
+ use: {
44
+ /* Base URL to use in actions like `await page.goto('')`.
45
+ Uncomment and set if all tests use the same domain. */
46
+ // baseURL: 'http://localhost:3000',
47
+
48
+ /* Collect trace when retrying the failed test.
49
+ Traces help debug failures. See https://playwright.dev/docs/trace-viewer */
50
+ trace: 'on-first-retry',
51
+ },
52
+
53
+ /* Configure projects for different browsers */
54
+ projects: [
55
+ {
56
+ // Test configuration for Google Chrome desktop browser
57
+ name: 'chromium',
58
+ use: { ...devices['Desktop Chrome'] },
59
+ },
60
+
61
+ // {
62
+ // // Test configuration for Mozilla Firefox desktop browser
63
+ // name: 'firefox',
64
+ // use: { ...devices['Desktop Firefox'] },
65
+ // },
66
+
67
+ // {
68
+ // // Test configuration for Apple Safari desktop browser
69
+ // name: 'webkit',
70
+ // use: { ...devices['Desktop Safari'] },
71
+ // },
72
+
73
+ /* Test against mobile viewports. */
74
+ // {
75
+ // name: 'Mobile Chrome',
76
+ // use: { ...devices['Pixel 5'] },
77
+ // },
78
+ // {
79
+ // name: 'Mobile Safari',
80
+ // use: { ...devices['iPhone 12'] },
81
+ // },
82
+
83
+ /* Test against branded browsers. */
84
+ // {
85
+ // name: 'Microsoft Edge',
86
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
87
+ // },
88
+ // {
89
+ // name: 'Google Chrome',
90
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
91
+ // },
92
+ ],
93
+
94
+ /* Run your local dev server before starting the tests.
95
+ Uncomment and configure if you need to start a local server for testing. */
96
+ // webServer: {
97
+ // command: 'npm run start', // Command to start the server
98
+ // url: 'http://localhost:3000', // URL to wait for before running tests
99
+ // reuseExistingServer: !process.env.CI, // Reuse server if already running (not on CI)
100
+ // },
101
+ });
102
+
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Auto-Healing Utilities Module
3
+ * This module contains functions for automatically finding alternative elements
4
+ * when a selector fails. It extracts keywords from selectors and searches the DOM
5
+ * for similar elements that might be the intended target.
6
+ */
7
+
8
+ /**
9
+ * Extracts meaningful keywords from a selector string for auto-healing.
10
+ * Supports both CSS and XPath selectors by parsing them differently.
11
+ * @param {string} selector - The CSS or XPath selector to analyze
12
+ * @returns {string[]} Array of lowercase keywords extracted from the selector
13
+ */
14
+ function extractKeywords(selector) {
15
+ // Check if this is an XPath selector (starts with / or //)
16
+ if (selector.startsWith('/') || selector.startsWith('//')) {
17
+ // XPath parsing logic
18
+ const keywords = [];
19
+
20
+ // Extract tag names like 'input', 'button' from //tagName
21
+ const tagMatch = selector.match(/\/\/(\w+)/);
22
+ if (tagMatch) keywords.push(tagMatch[1]);
23
+
24
+ // Extract attribute values like @id='username' -> 'username'
25
+ const attrMatches = selector.match(/@[\w-]+='([^']+)'/g);
26
+ if (attrMatches) {
27
+ attrMatches.forEach(match => {
28
+ const value = match.match(/'([^']+)'/)?.[1];
29
+ if (value) keywords.push(value);
30
+ });
31
+ }
32
+
33
+ // Extract text content like text()='Submit' -> 'Submit'
34
+ const textMatch = selector.match(/text\(\)='([^']+)'/);
35
+ if (textMatch) keywords.push(textMatch[1]);
36
+
37
+ // Convert all keywords to lowercase for case-insensitive matching
38
+ return keywords.map(word => word.toLowerCase());
39
+ } else {
40
+ // CSS parsing logic
41
+ return selector
42
+ .replace(/[#._-]/g, ' ') // Replace special chars with spaces
43
+ .split(' ') // Split into words
44
+ .filter(Boolean) // Remove empty strings
45
+ .map(word => word.toLowerCase()); // Convert to lowercase
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Finds candidate elements in the DOM that match the extracted keywords.
51
+ * Searches for elements likely to be interactive based on the intended action.
52
+ * @param {Page} page - The Playwright page object
53
+ * @param {string[]} keywords - Keywords extracted from the failed selector
54
+ * @param {string} [action='click'] - The intended action ('click' or 'fill') to optimize search
55
+ * @returns {Locator[]} Array of candidate element locators, sorted by relevance
56
+ */
57
+ async function findCandidates(page, keywords, action = 'click') {
58
+ let selector;
59
+
60
+ // Choose different element types based on the intended action
61
+ if (action === 'fill') {
62
+ // For filling, look for input elements that can receive text
63
+ selector = 'input, textarea, select';
64
+ } else {
65
+ // For clicking, look for clickable elements
66
+ selector = 'button, a, input[type="button"], input[type="submit"]';
67
+ }
68
+
69
+ // Get all matching elements on the page
70
+ const elements = await page.locator(selector).all();
71
+
72
+ const matches = [];
73
+
74
+ // Evaluate each element to see how well it matches our keywords
75
+ for (const el of elements) {
76
+ // Get the visible text content (if any)
77
+ const text = (await el.innerText().catch(() => '')).toLowerCase();
78
+ // Get the aria-label attribute (if any)
79
+ const aria = (await el.getAttribute('aria-label') || '').toLowerCase();
80
+ // Get the name attribute (common for form inputs)
81
+ const name = (await el.getAttribute('name') || '').toLowerCase();
82
+ // Get the id attribute
83
+ const id = (await el.getAttribute('id') || '').toLowerCase();
84
+ // Get the placeholder attribute (for inputs)
85
+ const placeholder = (await el.getAttribute('placeholder') || '').toLowerCase();
86
+
87
+ let score = 0; // Relevance score for this element
88
+
89
+ // Check each keyword against various element properties
90
+ for (const key of keywords) {
91
+ if (text.includes(key)) score += 2; // Text content is most important
92
+ if (aria.includes(key)) score += 1; // ARIA label is helpful
93
+ if (name.includes(key)) score += 1; // Name attribute
94
+ if (id.includes(key)) score += 1; // ID attribute
95
+ if (placeholder.includes(key)) score += 1; // Placeholder text
96
+ }
97
+
98
+ // If the element has any matching keywords, add it to candidates
99
+ if (score > 0) {
100
+ matches.push({ el, score });
101
+ }
102
+ }
103
+
104
+ // Sort candidates by score (highest first) and return just the elements
105
+ matches.sort((a, b) => b.score - a.score);
106
+
107
+ return matches.map(m => m.el);
108
+ }
109
+
110
+ // Export the functions for use in other modules
111
+ export { extractKeywords, findCandidates };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Core Healing Logic Module
3
+ * This module contains the fundamental healing algorithms used by both regular and auto modes.
4
+ * It handles the process of trying multiple locators until one works.
5
+ */
6
+
7
+ // Import functions for resolving locators and detecting selector types
8
+ import { resolveLocator, detectSelectorType } from './strategies.js';
9
+ // Import logging utility
10
+ import { log } from './logger.js';
11
+
12
+ /**
13
+ * Core healing function that tries multiple locators in sequence.
14
+ * @param {Page} page - The Playwright page object
15
+ * @param {Object} options - Healing configuration
16
+ * @param {string} options.primary - The primary selector to try first
17
+ * @param {Array} options.fallbacks - Array of fallback selector configurations
18
+ * @param {number} [options.timeout=2000] - Timeout in ms for each locator attempt
19
+ * @param {boolean} [options.log=true] - Whether to enable logging
20
+ * @returns {Locator} The first working locator found
21
+ * @throws {Error} If no locators work
22
+ */
23
+ async function regularHeal(page, options) {
24
+ // Extract configuration options with default values
25
+ const {
26
+ primary, // The main selector to try
27
+ fallbacks = [], // Additional selectors to try if primary fails
28
+ timeout = 2000, // How long to wait for each selector
29
+ log: enableLog = true // Whether to show progress logs
30
+ } = options;
31
+
32
+ // Create an array of all locator attempts, starting with the primary
33
+ // Automatically detect if primary is CSS or XPath
34
+ const attempts = [
35
+ { type: detectSelectorType(primary), value: primary },
36
+ ...fallbacks // Add all fallback locators
37
+ ];
38
+
39
+ // Try each locator in order until one works
40
+ for (let attempt of attempts) {
41
+ try {
42
+ // Log which locator we're trying
43
+ log(`Trying: ${JSON.stringify(attempt)}`, enableLog);
44
+
45
+ // Convert the locator configuration to a Playwright locator
46
+ const locator = resolveLocator(page, attempt);
47
+
48
+ // Wait for the element to be visible (this will throw if not found/visible)
49
+ await locator.waitFor({
50
+ state: 'visible',
51
+ timeout
52
+ });
53
+
54
+ // Success! Log and return the working locator
55
+ log(`✅ Success`, enableLog);
56
+
57
+ return locator;
58
+
59
+ } catch (err) {
60
+ // This locator failed, log the failure and try the next one
61
+ log(`❌ Failed`, enableLog);
62
+ }
63
+ }
64
+
65
+ // All locators failed, throw an error with details
66
+ throw new Error('Regular mode failed: All locators failed');
67
+ }
68
+
69
+ // Export the main healing function
70
+ export { regularHeal };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Logging Utility Module
3
+ * Provides a simple logging function for the healing process.
4
+ * All log messages are prefixed with '[Healing]' for easy identification.
5
+ */
6
+
7
+ /**
8
+ * Logs a message to the console if logging is enabled.
9
+ * @param {string} message - The message to log
10
+ * @param {boolean} [enabled=true] - Whether to actually log the message
11
+ */
12
+ function log(message, enabled = true) {
13
+ // Only log if logging is enabled (default is true)
14
+ if (enabled) {
15
+ // Prefix all messages with '[Healing]' for easy filtering
16
+ console.log(`[Healing] ${message}`);
17
+ }
18
+ }
19
+
20
+ // Export the logging function
21
+ export { log };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Locator Strategies Module
3
+ * This module handles different types of element selectors and converts them
4
+ * to Playwright locators. It supports CSS, XPath, text, ARIA labels, and roles.
5
+ */
6
+
7
+ /**
8
+ * Detects the type of selector string (CSS or XPath).
9
+ * @param {string} selector - The selector string to analyze
10
+ * @returns {string} 'xpath' if it starts with '/' or '//', otherwise 'css'
11
+ */
12
+ function detectSelectorType(selector) {
13
+ // XPath selectors always start with '/' or '//'
14
+ if (selector.startsWith('/') || selector.startsWith('//')) {
15
+ return 'xpath';
16
+ }
17
+ // Default to CSS for all other selectors
18
+ // Could be extended to detect other types like data-testid, etc.
19
+ return 'css';
20
+ }
21
+
22
+ /**
23
+ * Converts a strategy configuration to a Playwright locator.
24
+ * @param {Page} page - The Playwright page object
25
+ * @param {Object} strategy - The locator strategy configuration
26
+ * @param {string} strategy.type - The type of locator ('css', 'xpath', 'text', 'aria', 'role')
27
+ * @param {string} strategy.value - The selector value
28
+ * @param {string} [strategy.name] - For role strategies, the accessible name
29
+ * @returns {Locator} A Playwright locator object
30
+ * @throws {Error} If the strategy type is not supported
31
+ */
32
+ function resolveLocator(page, strategy) {
33
+ // Use a switch statement to handle different locator types
34
+ switch (strategy.type) {
35
+ case 'css':
36
+ // Standard CSS selector like '#id', '.class', 'input[type="text"]'
37
+ return page.locator(strategy.value);
38
+
39
+ case 'xpath':
40
+ // XPath selector like '//input[@id="username"]'
41
+ return page.locator(strategy.value);
42
+
43
+ case 'text':
44
+ // Find element by its visible text content
45
+ return page.getByText(strategy.value);
46
+
47
+ case 'aria':
48
+ // Find element by its aria-label attribute
49
+ return page.getByLabel(strategy.value);
50
+
51
+ case 'role':
52
+ // Find element by ARIA role and accessible name
53
+ return page.getByRole(strategy.value, {
54
+ name: strategy.name // The accessible name for the role
55
+ });
56
+
57
+ default:
58
+ // Unknown strategy type, throw an error with details
59
+ throw new Error(`Unsupported strategy: ${JSON.stringify(strategy)}`);
60
+ }
61
+ }
62
+
63
+ // Export the functions for use in other modules
64
+ export { resolveLocator, detectSelectorType };
package/src/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Main entry point for the Playwright Healing Locators library.
3
+ * This library provides self-healing locator strategies for Playwright tests,
4
+ * allowing tests to automatically find alternative selectors when the primary one fails.
5
+ */
6
+
7
+ // Import the regular healing mode module, which provides fill and click methods with fallback support
8
+ import * as regular from './modes/regular.js';
9
+
10
+ // Import the auto-healing mode module, which provides intelligent self-healing without manual fallbacks
11
+ import * as auto from './modes/auto.js';
12
+
13
+ // Export the healing modules with descriptive names
14
+ // regularHeal: Use this for tests where you want to specify fallback locators manually
15
+ // autoHeal: Use this for tests where you want automatic healing based on element analysis
16
+ export { regular as regularHeal, auto as autoHeal };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Auto-Healing Mode Module
3
+ * This module provides intelligent, automatic healing for Playwright locators.
4
+ * When a selector fails, it analyzes the selector to extract keywords and finds
5
+ * similar elements on the page that might be the intended target.
6
+ */
7
+
8
+ // Import the core healing function for basic locator resolution
9
+ import { regularHeal } from '../core/healer.js';
10
+ // Import functions for keyword extraction and candidate finding
11
+ import { extractKeywords, findCandidates } from '../core/autoHeal.js';
12
+ // Import logging utility for debugging and progress tracking
13
+ import { log } from '../core/logger.js';
14
+
15
+ /**
16
+ * Core auto-healing logic that tries to find a working locator automatically.
17
+ * @param {Page} page - The Playwright page object
18
+ * @param {Object} options - Configuration object
19
+ * @param {string} options.primary - The failed selector to analyze
20
+ * @param {number} [options.timeout=2000] - Timeout for element waiting
21
+ * @param {boolean} [options.log=true] - Whether to log healing process
22
+ * @param {string} [action='click'] - The intended action ('click' or 'fill') to optimize candidate search
23
+ * @returns {Locator} A working Playwright locator
24
+ * @throws {Error} If no suitable element can be found
25
+ */
26
+ async function autoHealLocator(page, options, action = 'click') {
27
+ try {
28
+ // First, try the primary locator without any fallbacks
29
+ return await regularHeal(page, { ...options, fallbacks: [] });
30
+
31
+ } catch (err) {
32
+ // Primary locator failed, start auto-healing process
33
+ log('🤖 Auto-healing started...', options.log);
34
+
35
+ // Extract meaningful keywords from the failed selector
36
+ const keywords = extractKeywords(options.primary);
37
+ log(`Keywords: ${keywords.join(', ')}`, options.log);
38
+
39
+ // Find potential matching elements based on the keywords
40
+ const candidates = await findCandidates(page, keywords, action);
41
+
42
+ // Try the top candidates (limited to 4 attempts to avoid too many tries)
43
+ for (let i = 0; i < candidates.length; i++) {
44
+ if (i > 3) break; // Limit attempts to prevent excessive waiting
45
+
46
+ try {
47
+ const el = candidates[i];
48
+
49
+ // Wait for the candidate element to be visible
50
+ await el.waitFor({
51
+ state: 'visible',
52
+ timeout: 2000
53
+ });
54
+
55
+ // Success! Log and return the working element
56
+ log(`✅ Auto-healed using candidate ${i + 1}`, options.log);
57
+
58
+ return el;
59
+
60
+ } catch (e) {
61
+ // This candidate didn't work, try the next one
62
+ log(`❌ Candidate ${i + 1} failed`, options.log);
63
+ }
64
+ }
65
+
66
+ // All candidates failed, throw an error
67
+ throw new Error('Auto-healing failed: No suitable element found');
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Performs a click action using auto-healing strategy.
73
+ * @param {Page} page - The Playwright page object
74
+ * @param {Object} options - Configuration object with primary selector
75
+ */
76
+ async function click(page, options) {
77
+ const locator = await autoHealLocator(page, options, 'click');
78
+ await locator.click();
79
+ }
80
+
81
+ /**
82
+ * Performs a fill action using auto-healing strategy.
83
+ * @param {Page} page - The Playwright page object
84
+ * @param {Object} options - Configuration object with primary selector
85
+ * @param {string} options.primary - The selector to try (will auto-heal if it fails)
86
+ * @param {number} [options.timeout=2000] - Timeout for element waiting
87
+ * @param {boolean} [options.log=true] - Whether to log the healing process
88
+ * @param {string} value - The text value to fill into the input field
89
+ */
90
+ async function fill(page, options, value) {
91
+ // Use the auto-healing locator function with 'fill' action for optimization
92
+ const locator = await autoHealLocator(page, options, 'fill');
93
+ // Fill the input field with the provided value
94
+ await locator.fill(value);
95
+ }
96
+
97
+ // Export the functions for use in other modules
98
+ export { click, fill };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Regular Healing Mode Module
3
+ * This module provides manual fallback-based healing for Playwright locators.
4
+ * When a primary selector fails, it tries a series of fallback selectors in order.
5
+ */
6
+
7
+ // Import the core healing function that handles the logic of trying selectors
8
+ import { regularHeal } from '../core/healer.js';
9
+
10
+ /**
11
+ * Performs a click action using regular healing strategy.
12
+ * @param {Page} page - The Playwright page object
13
+ * @param {Object} options - Configuration object containing primary selector and fallbacks
14
+ * @param {string} options.primary - The main CSS or XPath selector to try first
15
+ * @param {Array} options.fallbacks - Array of fallback selector objects with type and value
16
+ * @param {number} [options.timeout=2000] - Timeout in milliseconds for each selector attempt
17
+ * @param {boolean} [options.log=true] - Whether to log healing attempts to console
18
+ */
19
+ async function click(page, options) {
20
+ // Call the core healing function to get a working locator
21
+ const locator = await regularHeal(page, options);
22
+ // Perform the click action on the found locator
23
+ await locator.click();
24
+ }
25
+
26
+ /**
27
+ * Performs a fill action using regular healing strategy.
28
+ * @param {Page} page - The Playwright page object
29
+ * @param {Object} options - Configuration object containing primary selector and fallbacks
30
+ * @param {string} options.primary - The main CSS or XPath selector to try first
31
+ * @param {Array} options.fallbacks - Array of fallback selector objects with type and value
32
+ * @param {number} [options.timeout=2000] - Timeout in milliseconds for each selector attempt
33
+ * @param {boolean} [options.log=true] - Whether to log healing attempts to console
34
+ * @param {string} value - The text value to fill into the input field
35
+ */
36
+ async function fill(page, options, value) {
37
+ // Call the core healing function to get a working locator
38
+ const locator = await regularHeal(page, options);
39
+ // Fill the input field with the provided value
40
+ await locator.fill(value);
41
+ }
42
+
43
+ // Export the functions so they can be used by other modules
44
+ export { click, fill };