playwright-windows-wrapper 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.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # playwright-windows-wrapper
2
+
3
+ A unified, hybrid test automation wrapper that bridges **Playwright** (for web automation) and **Appium/WinAppDriver** (for native Windows desktop applications). Write single-script end-to-end flows that seamlessly jump between standard browsers and native `.exe` applications.
4
+
5
+ ---
6
+
7
+ ## 🚀 Key Features
8
+
9
+ * **Unified API:** Control both web elements and native desktop UI components using one client interface.
10
+ * **Advanced Desktop Interactions:** Right-clicks, double-clicks, drag-and-drop, and hardware keyboard shortcuts (`Ctrl + A`, etc.) mapped natively to Windows.
11
+ * **Window Controls:** Maximize, minimize, and inspect native application windows easily.
12
+
13
+ ---
14
+
15
+ ## 🛠️ Prerequisites
16
+
17
+ Because Windows desktop automation requires access to OS-level accessibility trees, you must configure your host machine before running desktop tests:
18
+
19
+ 1. **Enable Windows Developer Mode:**
20
+ Go to **Settings > Update & Security > For developers** and toggle **Developer Mode** to **On**.
21
+ 2. **Install Appium Globally:**
22
+ ```bash
23
+ npm install -g appium
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const playwright_1 = require("playwright");
4
+ const webdriverio_1 = require("webdriverio");
5
+ const webdriverio_2 = require("webdriverio"); // Used for native key simulations
6
+ class UltimateAutomationWrapper {
7
+ webBrowser = null;
8
+ webPage = null;
9
+ desktopSession = null;
10
+ // ==========================================
11
+ // INITIALIZATION
12
+ // ==========================================
13
+ async initWeb() {
14
+ this.webBrowser = await playwright_1.chromium.launch({ headless: false });
15
+ this.webPage = await this.webBrowser.newPage();
16
+ return this.webPage;
17
+ }
18
+ async initDesktop(appPathOrId) {
19
+ const appiumOptions = {
20
+ hostname: '127.0.0.1',
21
+ port: 4723,
22
+ logLevel: 'error',
23
+ capabilities: {
24
+ platformName: 'Windows',
25
+ 'appium:automationName': 'windows',
26
+ 'appium:app': appPathOrId,
27
+ }
28
+ };
29
+ this.desktopSession = await (0, webdriverio_1.remote)(appiumOptions);
30
+ return this.desktopSession;
31
+ }
32
+ // ==========================================
33
+ // WINDOW STATE OPERATIONS
34
+ // ==========================================
35
+ async windowMaximize() {
36
+ if (!this.desktopSession)
37
+ throw new Error('Desktop session uninitialized.');
38
+ await this.desktopSession.maximizeWindow();
39
+ }
40
+ async windowMinimize() {
41
+ if (!this.desktopSession)
42
+ throw new Error('Desktop session uninitialized.');
43
+ await this.desktopSession.minimizeWindow();
44
+ }
45
+ async getWindowTitle() {
46
+ if (!this.desktopSession)
47
+ throw new Error('Desktop session uninitialized.');
48
+ return await this.desktopSession.getTitle();
49
+ }
50
+ // ==========================================
51
+ // BASIC & ADVANCED MOUSE OPERATIONS
52
+ // ==========================================
53
+ async click(selector) {
54
+ const el = await this.desktopSession.$(selector);
55
+ await el.click();
56
+ }
57
+ async doubleClick(selector) {
58
+ const el = await this.desktopSession.$(selector);
59
+ await el.doubleClick();
60
+ }
61
+ async rightClick(selector) {
62
+ const el = await this.desktopSession.$(selector);
63
+ // Uses WebdriverIO / Appium W3C Actions for a custom context-click context
64
+ await this.desktopSession.action('pointer')
65
+ .move({ origin: el })
66
+ .down({ button: 2 }) // 2 is the Right Mouse Button
67
+ .up({ button: 2 })
68
+ .perform();
69
+ }
70
+ async dragAndDrop(sourceSelector, targetSelector) {
71
+ const source = await this.desktopSession.$(sourceSelector);
72
+ const target = await this.desktopSession.$(targetSelector);
73
+ await this.desktopSession.action('pointer')
74
+ .move({ origin: source })
75
+ .down({ button: 0 }) // Left click hold
76
+ .pause(100)
77
+ .move({ origin: target, duration: 500 }) // Smooth drag
78
+ .up({ button: 0 }) // Release
79
+ .perform();
80
+ }
81
+ // ==========================================
82
+ // KEYBOARD & INPUT OPERATIONS
83
+ // ==========================================
84
+ async typeText(selector, text) {
85
+ const el = await this.desktopSession.$(selector);
86
+ await el.setValue(text);
87
+ }
88
+ async clearInput(selector) {
89
+ const el = await this.desktopSession.$(selector);
90
+ await el.clearValue();
91
+ }
92
+ /**
93
+ * Simulates hotkey combinations (e.g., Ctrl + S, Ctrl + A)
94
+ */
95
+ async pressShortcut(modifier, letter) {
96
+ // Maps semantic keys to WebdriverIO Key Enums
97
+ const modKey = modifier.toLowerCase() === 'ctrl' ? webdriverio_2.Key.Control : webdriverio_2.Key.Alt;
98
+ await this.desktopSession.action('key')
99
+ .down(modKey)
100
+ .down(letter)
101
+ .up(letter)
102
+ .up(modKey)
103
+ .perform();
104
+ }
105
+ async pressGlobalKey(keyName) {
106
+ // Sends independent key strokes like 'Enter', 'Tab', 'Escape'
107
+ await this.desktopSession.keys([keyName]);
108
+ }
109
+ // ==========================================
110
+ // INSPECTION & ASSERTION OPERATIONS
111
+ // ==========================================
112
+ async isElementDisplayed(selector) {
113
+ const el = await this.desktopSession.$(selector);
114
+ return await el.isDisplayed();
115
+ }
116
+ async isElementEnabled(selector) {
117
+ const el = await this.desktopSession.$(selector);
118
+ return await el.isEnabled();
119
+ }
120
+ async getElementText(selector) {
121
+ const el = await this.desktopSession.$(selector);
122
+ return await el.getText();
123
+ }
124
+ // ==========================================
125
+ // TEARDOWN
126
+ // ==========================================
127
+ async closeAll() {
128
+ if (this.webBrowser)
129
+ await this.webBrowser.close();
130
+ if (this.desktopSession)
131
+ await this.desktopSession.deleteSession();
132
+ }
133
+ }
134
+ // ==========================================
135
+ // RUNNING THE WORKFLOW IN WINDOWS CALC & NOTEPAD
136
+ // ==========================================
137
+ (async () => {
138
+ const driver = new UltimateAutomationWrapper();
139
+ try {
140
+ console.log('--- Instantiating Windows Calculator App ---');
141
+ // 'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App' is the AppID for standard Win10/Win11 calc
142
+ await driver.initDesktop('Microsoft.WindowsCalculator_8wekyb3d8bbwe!App');
143
+ await driver.windowMaximize();
144
+ console.log('Performing native UI Math: 7 x 8 =');
145
+ await driver.click('~num7Button'); // '~' is WebdriverIO shorthand for AccessibilityId
146
+ await driver.click('~multiplyButton');
147
+ await driver.click('~num8Button');
148
+ await driver.click('~equalButton');
149
+ const result = await driver.getElementText('~CalculatorResults');
150
+ console.log(`Retrieved UI Text State: "${result}"`);
151
+ // Clean up Calculator session to open up Notepad
152
+ await driver.closeAll();
153
+ console.log('\n--- Instantiating Windows Notepad App ---');
154
+ await driver.initDesktop('notepad.exe');
155
+ console.log('Typing Text & Clearing Input...');
156
+ await driver.typeText('//*[@Name="Text Editor"]', 'This text will be replaced.');
157
+ await driver.clearInput('//*[@Name="Text Editor"]');
158
+ console.log('Typing formal entry...');
159
+ await driver.typeText('//*[@Name="Text Editor"]', 'Final clean text string automation input.');
160
+ console.log('Executing Windows Hotkeys (Ctrl + A)...');
161
+ await driver.pressShortcut('ctrl', 'a');
162
+ console.log('Sending Backspace key to erase execution selection...');
163
+ await driver.pressGlobalKey(webdriverio_2.Key.Backspace);
164
+ }
165
+ catch (err) {
166
+ console.error('Automation Runtime Failure:', err);
167
+ }
168
+ finally {
169
+ await driver.closeAll();
170
+ console.log('Wrapper execution cleaned up safely.');
171
+ }
172
+ })();
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "playwright-windows-wrapper",
3
+ "version": "1.0.0",
4
+ "description": "A unified automation wrapper combining Playwright and Appium for hybrid web and Windows native testing.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "npx tsc",
9
+ "prepare": "npm run build"
10
+ },
11
+ "keywords": [
12
+ "playwright",
13
+ "appium",
14
+ "windows",
15
+ "automation",
16
+ "testing"
17
+ ],
18
+ "author": "Your Name",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "appium": "^2.0.0",
22
+ "playwright": "^1.40.0",
23
+ "webdriverio": "^8.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.0.0"
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,194 @@
1
+ import { chromium, Browser as PWBrowser, Page as PWPage } from 'playwright';
2
+ import { remote, RemoteOptions, Browser as AppiumBrowser } from 'webdriverio';
3
+ import { Key } from 'webdriverio'; // Used for native key simulations
4
+
5
+ class UltimateAutomationWrapper {
6
+ private webBrowser: PWBrowser | null = null;
7
+ private webPage: PWPage | null = null;
8
+ private desktopSession: AppiumBrowser | null = null;
9
+
10
+ // ==========================================
11
+ // INITIALIZATION
12
+ // ==========================================
13
+ async initWeb(): Promise<PWPage> {
14
+ this.webBrowser = await chromium.launch({ headless: false });
15
+ this.webPage = await this.webBrowser.newPage();
16
+ return this.webPage;
17
+ }
18
+
19
+ async initDesktop(appPathOrId: string): Promise<AppiumBrowser> {
20
+ const appiumOptions: RemoteOptions = {
21
+ hostname: '127.0.0.1',
22
+ port: 4723,
23
+ logLevel: 'error',
24
+ capabilities: {
25
+ platformName: 'Windows',
26
+ 'appium:automationName': 'windows',
27
+ 'appium:app': appPathOrId,
28
+ }
29
+ };
30
+ this.desktopSession = await remote(appiumOptions);
31
+ return this.desktopSession;
32
+ }
33
+
34
+ // ==========================================
35
+ // WINDOW STATE OPERATIONS
36
+ // ==========================================
37
+ async windowMaximize(): Promise<void> {
38
+ if (!this.desktopSession) throw new Error('Desktop session uninitialized.');
39
+ await this.desktopSession.maximizeWindow();
40
+ }
41
+
42
+ async windowMinimize(): Promise<void> {
43
+ if (!this.desktopSession) throw new Error('Desktop session uninitialized.');
44
+ await this.desktopSession.minimizeWindow();
45
+ }
46
+
47
+ async getWindowTitle(): Promise<string> {
48
+ if (!this.desktopSession) throw new Error('Desktop session uninitialized.');
49
+ return await this.desktopSession.getTitle();
50
+ }
51
+
52
+ // ==========================================
53
+ // BASIC & ADVANCED MOUSE OPERATIONS
54
+ // ==========================================
55
+ async click(selector: string): Promise<void> {
56
+ const el = await this.desktopSession!.$(selector);
57
+ await el.click();
58
+ }
59
+
60
+ async doubleClick(selector: string): Promise<void> {
61
+ const el = await this.desktopSession!.$(selector);
62
+ await el.doubleClick();
63
+ }
64
+
65
+ async rightClick(selector: string): Promise<void> {
66
+ const el = await this.desktopSession!.$(selector);
67
+ // Uses WebdriverIO / Appium W3C Actions for a custom context-click context
68
+ await this.desktopSession!.action('pointer')
69
+ .move({ origin: el })
70
+ .down({ button: 2 }) // 2 is the Right Mouse Button
71
+ .up({ button: 2 })
72
+ .perform();
73
+ }
74
+
75
+ async dragAndDrop(sourceSelector: string, targetSelector: string): Promise<void> {
76
+ const source = await this.desktopSession!.$(sourceSelector);
77
+ const target = await this.desktopSession!.$(targetSelector);
78
+
79
+ await this.desktopSession!.action('pointer')
80
+ .move({ origin: source })
81
+ .down({ button: 0 }) // Left click hold
82
+ .pause(100)
83
+ .move({ origin: target, duration: 500 }) // Smooth drag
84
+ .up({ button: 0 }) // Release
85
+ .perform();
86
+ }
87
+
88
+ // ==========================================
89
+ // KEYBOARD & INPUT OPERATIONS
90
+ // ==========================================
91
+ async typeText(selector: string, text: string): Promise<void> {
92
+ const el = await this.desktopSession!.$(selector);
93
+ await el.setValue(text);
94
+ }
95
+
96
+ async clearInput(selector: string): Promise<void> {
97
+ const el = await this.desktopSession!.$(selector);
98
+ await el.clearValue();
99
+ }
100
+
101
+ /**
102
+ * Simulates hotkey combinations (e.g., Ctrl + S, Ctrl + A)
103
+ */
104
+ async pressShortcut(modifier: string, letter: string): Promise<void> {
105
+ // Maps semantic keys to WebdriverIO Key Enums
106
+ const modKey = modifier.toLowerCase() === 'ctrl' ? Key.Control : Key.Alt;
107
+
108
+ await this.desktopSession!.action('key')
109
+ .down(modKey)
110
+ .down(letter)
111
+ .up(letter)
112
+ .up(modKey)
113
+ .perform();
114
+ }
115
+
116
+ async pressGlobalKey(keyName: string): Promise<void> {
117
+ // Sends independent key strokes like 'Enter', 'Tab', 'Escape'
118
+ await this.desktopSession!.keys([keyName]);
119
+ }
120
+
121
+ // ==========================================
122
+ // INSPECTION & ASSERTION OPERATIONS
123
+ // ==========================================
124
+ async isElementDisplayed(selector: string): Promise<boolean> {
125
+ const el = await this.desktopSession!.$(selector);
126
+ return await el.isDisplayed();
127
+ }
128
+
129
+ async isElementEnabled(selector: string): Promise<boolean> {
130
+ const el = await this.desktopSession!.$(selector);
131
+ return await el.isEnabled();
132
+ }
133
+
134
+ async getElementText(selector: string): Promise<string> {
135
+ const el = await this.desktopSession!.$(selector);
136
+ return await el.getText();
137
+ }
138
+
139
+ // ==========================================
140
+ // TEARDOWN
141
+ // ==========================================
142
+ async closeAll(): Promise<void> {
143
+ if (this.webBrowser) await this.webBrowser.close();
144
+ if (this.desktopSession) await this.desktopSession.deleteSession();
145
+ }
146
+ }
147
+
148
+ // ==========================================
149
+ // RUNNING THE WORKFLOW IN WINDOWS CALC & NOTEPAD
150
+ // ==========================================
151
+ (async () => {
152
+ const driver = new UltimateAutomationWrapper();
153
+
154
+ try {
155
+ console.log('--- Instantiating Windows Calculator App ---');
156
+ // 'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App' is the AppID for standard Win10/Win11 calc
157
+ await driver.initDesktop('Microsoft.WindowsCalculator_8wekyb3d8bbwe!App');
158
+ await driver.windowMaximize();
159
+
160
+ console.log('Performing native UI Math: 7 x 8 =');
161
+ await driver.click('~num7Button'); // '~' is WebdriverIO shorthand for AccessibilityId
162
+ await driver.click('~multiplyButton');
163
+ await driver.click('~num8Button');
164
+ await driver.click('~equalButton');
165
+
166
+ const result = await driver.getElementText('~CalculatorResults');
167
+ console.log(`Retrieved UI Text State: "${result}"`);
168
+
169
+ // Clean up Calculator session to open up Notepad
170
+ await driver.closeAll();
171
+
172
+ console.log('\n--- Instantiating Windows Notepad App ---');
173
+ await driver.initDesktop('notepad.exe');
174
+
175
+ console.log('Typing Text & Clearing Input...');
176
+ await driver.typeText('//*[@Name="Text Editor"]', 'This text will be replaced.');
177
+ await driver.clearInput('//*[@Name="Text Editor"]');
178
+
179
+ console.log('Typing formal entry...');
180
+ await driver.typeText('//*[@Name="Text Editor"]', 'Final clean text string automation input.');
181
+
182
+ console.log('Executing Windows Hotkeys (Ctrl + A)...');
183
+ await driver.pressShortcut('ctrl', 'a');
184
+
185
+ console.log('Sending Backspace key to erase execution selection...');
186
+ await driver.pressGlobalKey(Key.Backspace);
187
+
188
+ } catch (err) {
189
+ console.error('Automation Runtime Failure:', err);
190
+ } finally {
191
+ await driver.closeAll();
192
+ console.log('Wrapper execution cleaned up safely.');
193
+ }
194
+ })();
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "CommonJS",
5
+ "lib": ["ES2022"],
6
+ "declaration": true, // Generates corresponding '.d.ts' file for type safety
7
+ "outDir": "./dist", // Redirect output structure to the directory
8
+ "rootDir": "./src", // Only look for TypeScript files inside /src
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true
13
+ },
14
+ "include": ["src/**/*"]
15
+ }