assure-testing 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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Assure Testing Language
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # ๐Ÿงช Assure - Custom Testing Language
2
+
3
+ **Assure** is a custom testing language (DSL) built from scratch with a unique browser automation engine using Chrome DevTools Protocol (CDP).
4
+
5
+ ## โœจ Features
6
+
7
+ - ๐ŸŽฏ **Custom Syntax** - Human-readable test commands
8
+ - ๐Ÿ”ง **Built from Scratch** - No Playwright, no Selenium - pure CDP implementation
9
+ - โšก **Lightweight** - Minimal dependencies
10
+ - ๐Ÿš€ **Fast** - Direct Chrome DevTools Protocol communication
11
+ - ๐Ÿ“ **Simple** - Easy to learn and write tests
12
+
13
+ ## ๐Ÿ—๏ธ Architecture
14
+
15
+ ```
16
+ assure/
17
+ โ”‚
18
+ โ”œโ”€โ”€ language/
19
+ โ”‚ โ”œโ”€โ”€ parser.ts # Parses Assure syntax
20
+ โ”‚ โ”œโ”€โ”€ tokenizer.ts # Tokenizes lines
21
+ โ”‚
22
+ โ”œโ”€โ”€ engine/
23
+ โ”‚ โ”œโ”€โ”€ browser.ts # Custom CDP browser engine
24
+ โ”‚ โ”œโ”€โ”€ executor.ts # Command executor
25
+ โ”‚
26
+ โ”œโ”€โ”€ commands/
27
+ โ”‚ โ”œโ”€โ”€ open.ts # OPEN command
28
+ โ”‚ โ”œโ”€โ”€ click.ts # CLICK command
29
+ โ”‚ โ”œโ”€โ”€ type.ts # TYPE command
30
+ โ”‚ โ”œโ”€โ”€ expect.ts # EXPECT command
31
+ โ”‚
32
+ โ”œโ”€โ”€ runner.ts # Main test runner
33
+ โ””โ”€โ”€ *.assure # Test files
34
+ ```
35
+
36
+ ## ๐Ÿ“ฆ Installation
37
+
38
+ ### Install from npm (Recommended)
39
+
40
+ ```bash
41
+ # Install globally
42
+ npm install -g assure-testing
43
+
44
+ # Or install locally in your project
45
+ npm install --save-dev assure-testing
46
+ ```
47
+
48
+ ### Install from source
49
+
50
+ ```bash
51
+ git clone https://github.com/yourusername/assure.git
52
+ cd assure
53
+ npm install
54
+ npm run build
55
+ ```
56
+
57
+ **Requirements:**
58
+ - Node.js 18+
59
+ - Chrome or Chromium browser installed
60
+
61
+ **File Extension:**
62
+ - All test files must use the `.assure` extension (e.g., `test.assure`, `login.assure`)
63
+
64
+ ## ๐Ÿš€ Quick Start
65
+
66
+ 1. **Create a test file with `.assure` extension** (`example.assure`):
67
+
68
+ ```
69
+ TEST "My First Test"
70
+
71
+ OPEN "https://example.com"
72
+ WAIT 2
73
+ EXPECT TITLE CONTAINS "Example"
74
+ ```
75
+
76
+ 2. **Install dependencies**:
77
+
78
+ ```bash
79
+ npm install
80
+ ```
81
+
82
+ 3. **Run the test**:
83
+
84
+ ```bash
85
+ # If installed globally
86
+ assure example.assure
87
+
88
+ # If installed locally
89
+ npx assure example.assure
90
+
91
+ # Or using npm script
92
+ npm test example.assure
93
+ ```
94
+
95
+ ## ๐Ÿ“š Language Syntax
96
+
97
+ ### Basic Commands
98
+
99
+ #### OPEN
100
+ Navigate to a URL:
101
+ ```
102
+ OPEN "https://example.com"
103
+ ```
104
+
105
+ #### CLICK
106
+ Click an element:
107
+ ```
108
+ CLICK "#button"
109
+ CLICK ".submit-btn"
110
+ CLICK "button[type='submit']"
111
+ ```
112
+
113
+ #### TYPE
114
+ Type text into an input:
115
+ ```
116
+ TYPE "#username" "admin"
117
+ TYPE "#password" "secret123"
118
+ ```
119
+
120
+ #### WAIT
121
+ Wait for specified seconds:
122
+ ```
123
+ WAIT 2
124
+ WAIT 5
125
+ ```
126
+
127
+ #### EXPECT
128
+ Assert conditions:
129
+
130
+ **Title:**
131
+ ```
132
+ EXPECT TITLE CONTAINS "Dashboard"
133
+ EXPECT TITLE EQUALS "My App"
134
+ ```
135
+
136
+ **URL:**
137
+ ```
138
+ EXPECT URL CONTAINS "/dashboard"
139
+ EXPECT URL EQUALS "https://example.com/home"
140
+ ```
141
+
142
+ **Text:**
143
+ ```
144
+ EXPECT TEXT "#welcome" CONTAINS "Welcome"
145
+ EXPECT TEXT ".message" EQUALS "Success"
146
+ ```
147
+
148
+ **Visibility:**
149
+ ```
150
+ EXPECT VISIBLE "#modal"
151
+ ```
152
+
153
+ ### Comments
154
+
155
+ Lines starting with `#` are comments:
156
+ ```
157
+ # This is a comment
158
+ OPEN "https://example.com"
159
+ ```
160
+
161
+ ### Test Labels
162
+
163
+ ```
164
+ TEST "User Login Flow"
165
+ ```
166
+
167
+ ## ๐Ÿ”ง Custom Browser Engine
168
+
169
+ Assure uses a **custom-built browser engine** that communicates directly with Chrome via Chrome DevTools Protocol (CDP). This gives you:
170
+
171
+ - **Full Control** - Direct access to browser internals
172
+ - **No Heavy Dependencies** - Just `chrome-remote-interface` for CDP
173
+ - **Unique Implementation** - Built specifically for Assure
174
+
175
+ ### How It Works
176
+
177
+ 1. Launches Chrome with remote debugging enabled
178
+ 2. Connects via WebSocket to Chrome DevTools Protocol
179
+ 3. Executes commands using CDP methods
180
+ 4. Handles element selection, clicking, typing, etc.
181
+
182
+ ## ๐Ÿ“ Example Test File
183
+
184
+ Save your tests with the `.assure` extension (e.g., `login.assure`, `checkout.assure`):
185
+
186
+ ```assure
187
+ TEST "User Login"
188
+
189
+ OPEN "https://example.com/login"
190
+
191
+ TYPE "#username" "admin"
192
+ TYPE "#password" "password123"
193
+ CLICK "#login-button"
194
+
195
+ WAIT 2
196
+
197
+ EXPECT URL CONTAINS "/dashboard"
198
+ EXPECT TEXT "#welcome" CONTAINS "Welcome"
199
+ EXPECT VISIBLE "#user-menu"
200
+ ```
201
+
202
+ ## ๐Ÿ› ๏ธ Configuration
203
+
204
+ ### Chrome Path
205
+
206
+ If Chrome is not in the default location, set the `CHROME_PATH` environment variable:
207
+
208
+ ```bash
209
+ export CHROME_PATH="/path/to/chrome"
210
+ node runner.js test.assure
211
+ ```
212
+
213
+ ### Headless Mode
214
+
215
+ Currently runs in headless mode by default. To run with visible browser, modify `runner.ts`:
216
+
217
+ ```typescript
218
+ session = await createBrowser(false); // visible browser
219
+ ```
220
+
221
+ ## ๐ŸŽฏ Supported Commands
222
+
223
+ | Command | Description | Example |
224
+ |---------|-------------|---------|
225
+ | `OPEN` | Navigate to URL | `OPEN "https://example.com"` |
226
+ | `CLICK` | Click element | `CLICK "#button"` |
227
+ | `TYPE` | Type text | `TYPE "#input" "text"` |
228
+ | `WAIT` | Wait seconds | `WAIT 2` |
229
+ | `EXPECT TITLE` | Assert title | `EXPECT TITLE CONTAINS "Page"` |
230
+ | `EXPECT URL` | Assert URL | `EXPECT URL CONTAINS "/home"` |
231
+ | `EXPECT TEXT` | Assert text | `EXPECT TEXT "#el" CONTAINS "text"` |
232
+ | `EXPECT VISIBLE` | Assert visibility | `EXPECT VISIBLE "#modal"` |
233
+ | `TEST` | Test label | `TEST "My Test"` |
234
+
235
+ ## ๐Ÿšง Future Enhancements
236
+
237
+ - [ ] Variables and functions
238
+ - [ ] IF/ELSE conditionals
239
+ - [ ] RETRY mechanisms
240
+ - [ ] Parallel test execution
241
+ - [ ] HTML/JSON reports
242
+ - [ ] Screenshot support
243
+ - [ ] VS Code syntax highlighting
244
+ - [ ] CI/CD integration
245
+
246
+ ## ๐Ÿ“„ License
247
+
248
+ MIT
249
+
250
+ ## ๐Ÿค Contributing
251
+
252
+ This is a custom testing language built from scratch. Feel free to extend it!
253
+
254
+ ---
255
+
256
+ **Built with โค๏ธ using Chrome DevTools Protocol**
257
+
@@ -0,0 +1,11 @@
1
+ /**
2
+ * CLICK command - Clicks an element
3
+ */
4
+ import { clickElement } from '../engine/browser.js';
5
+ export async function executeClick(session, args) {
6
+ if (args.length === 0) {
7
+ throw new Error('CLICK command requires a selector argument');
8
+ }
9
+ const selector = args[0];
10
+ await clickElement(session, selector);
11
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * EXPECT command - Asserts conditions
3
+ * Built from scratch using CDP
4
+ */
5
+ import { getTitle, getUrl, getTextContent, isVisible } from '../engine/browser.js';
6
+ export async function executeExpect(session, args) {
7
+ if (args.length < 2) {
8
+ throw new Error('EXPECT command requires at least 2 arguments');
9
+ }
10
+ const [target, condition, ...valueParts] = args;
11
+ const value = valueParts.join(' ');
12
+ switch (target.toUpperCase()) {
13
+ case 'TITLE':
14
+ const title = await getTitle(session);
15
+ if (condition.toUpperCase() === 'CONTAINS') {
16
+ if (!title.includes(value)) {
17
+ throw new Error(`Expected title to contain "${value}", but got "${title}"`);
18
+ }
19
+ }
20
+ else if (condition.toUpperCase() === 'EQUALS') {
21
+ if (title !== value) {
22
+ throw new Error(`Expected title to equal "${value}", but got "${title}"`);
23
+ }
24
+ }
25
+ else {
26
+ throw new Error(`Unknown condition for TITLE: ${condition}`);
27
+ }
28
+ break;
29
+ case 'URL':
30
+ const url = await getUrl(session);
31
+ if (condition.toUpperCase() === 'CONTAINS') {
32
+ if (!url.includes(value)) {
33
+ throw new Error(`Expected URL to contain "${value}", but got "${url}"`);
34
+ }
35
+ }
36
+ else if (condition.toUpperCase() === 'EQUALS') {
37
+ if (url !== value) {
38
+ throw new Error(`Expected URL to equal "${value}", but got "${url}"`);
39
+ }
40
+ }
41
+ else {
42
+ throw new Error(`Unknown condition for URL: ${condition}`);
43
+ }
44
+ break;
45
+ case 'TEXT':
46
+ if (args.length < 3) {
47
+ throw new Error('EXPECT TEXT requires a selector, condition, and value');
48
+ }
49
+ // For TEXT, the format is: EXPECT TEXT selector condition value
50
+ // But args already has target removed, so: [selector, condition, ...value]
51
+ const selector = args[0];
52
+ const textCondition = args[1];
53
+ const textValue = args.slice(2).join(' ');
54
+ const elementText = await getTextContent(session, selector);
55
+ if (textCondition.toUpperCase() === 'CONTAINS') {
56
+ if (!elementText.includes(textValue)) {
57
+ throw new Error(`Expected text to contain "${textValue}", but got "${elementText}"`);
58
+ }
59
+ }
60
+ else if (textCondition.toUpperCase() === 'EQUALS') {
61
+ if (elementText.trim() !== textValue.trim()) {
62
+ throw new Error(`Expected text to equal "${textValue}", but got "${elementText}"`);
63
+ }
64
+ }
65
+ else {
66
+ throw new Error(`Unknown condition for TEXT: ${textCondition}`);
67
+ }
68
+ break;
69
+ case 'VISIBLE':
70
+ const visibleSelector = args[0];
71
+ const visible = await isVisible(session, visibleSelector);
72
+ if (!visible) {
73
+ throw new Error(`Expected element "${visibleSelector}" to be visible`);
74
+ }
75
+ break;
76
+ default:
77
+ throw new Error(`Unknown EXPECT target: ${target}`);
78
+ }
79
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * OPEN command - Navigates to a URL
3
+ */
4
+ import { navigate } from '../engine/browser.js';
5
+ export async function executeOpen(session, args) {
6
+ if (args.length === 0) {
7
+ throw new Error('OPEN command requires a URL argument');
8
+ }
9
+ const url = args[0];
10
+ await navigate(session, url);
11
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * TYPE command - Types text into an input field
3
+ */
4
+ import { typeText } from '../engine/browser.js';
5
+ export async function executeType(session, args) {
6
+ if (args.length < 2) {
7
+ throw new Error('TYPE command requires a selector and text argument');
8
+ }
9
+ const selector = args[0];
10
+ const text = args.slice(1).join(' '); // Join in case text has spaces
11
+ await typeText(session, selector, text);
12
+ }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Browser Engine - Custom CDP-based browser automation
3
+ * Built from scratch using Chrome DevTools Protocol
4
+ */
5
+ import CDP from 'chrome-remote-interface';
6
+ import { spawn } from 'child_process';
7
+ import { setTimeout } from 'timers/promises';
8
+ import { existsSync } from 'fs';
9
+ const DEFAULT_CHROME_ARGS = [
10
+ '--headless',
11
+ '--disable-gpu',
12
+ '--no-sandbox',
13
+ '--disable-setuid-sandbox',
14
+ '--disable-dev-shm-usage',
15
+ '--remote-debugging-port=9222'
16
+ ];
17
+ /**
18
+ * Find Chrome/Chromium executable path
19
+ */
20
+ function findChromeExecutable() {
21
+ const platform = process.platform;
22
+ if (platform === 'darwin') {
23
+ // macOS
24
+ const paths = [
25
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
26
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
27
+ '/usr/bin/google-chrome',
28
+ '/usr/bin/chromium'
29
+ ];
30
+ for (const path of paths) {
31
+ if (existsSync(path))
32
+ return path;
33
+ }
34
+ }
35
+ else if (platform === 'linux') {
36
+ // Try common Linux Chrome/Chromium paths
37
+ const linuxPaths = ['google-chrome', 'chromium', 'chromium-browser'];
38
+ for (const chromePath of linuxPaths) {
39
+ // On Linux, we'll try to execute it to see if it exists
40
+ // For now, return the first one and let spawn handle errors
41
+ return chromePath;
42
+ }
43
+ return 'google-chrome'; // fallback
44
+ }
45
+ else if (platform === 'win32') {
46
+ const paths = [
47
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
48
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
49
+ ];
50
+ for (const path of paths) {
51
+ if (existsSync(path))
52
+ return path;
53
+ }
54
+ }
55
+ throw new Error('Chrome/Chromium not found. Please install Chrome or set CHROME_PATH environment variable.');
56
+ }
57
+ /**
58
+ * Launch Chrome and connect via CDP
59
+ */
60
+ export async function createBrowser(headless = true) {
61
+ const chromePath = process.env.CHROME_PATH || findChromeExecutable();
62
+ const args = headless ? DEFAULT_CHROME_ARGS : DEFAULT_CHROME_ARGS.filter(arg => arg !== '--headless');
63
+ // Launch Chrome
64
+ const chromeProcess = spawn(chromePath, args, {
65
+ stdio: 'ignore',
66
+ detached: false
67
+ });
68
+ // Wait for Chrome to start
69
+ await setTimeout(2000);
70
+ // Connect to Chrome via CDP
71
+ let client;
72
+ let retries = 10;
73
+ while (retries > 0) {
74
+ try {
75
+ client = await CDP({ port: 9222 });
76
+ break;
77
+ }
78
+ catch (error) {
79
+ retries--;
80
+ if (retries === 0) {
81
+ chromeProcess.kill();
82
+ throw new Error('Failed to connect to Chrome DevTools Protocol');
83
+ }
84
+ await setTimeout(500);
85
+ }
86
+ }
87
+ if (!client) {
88
+ chromeProcess.kill();
89
+ throw new Error('Failed to connect to Chrome DevTools Protocol');
90
+ }
91
+ // Enable required domains
92
+ const { Page, Runtime, DOM, Input, Network } = client;
93
+ await Page.enable();
94
+ await Runtime.enable();
95
+ await DOM.enable();
96
+ await Network.enable();
97
+ return {
98
+ chromeProcess,
99
+ client,
100
+ Page,
101
+ Runtime,
102
+ DOM,
103
+ Input,
104
+ Network
105
+ };
106
+ }
107
+ /**
108
+ * Close browser and cleanup
109
+ */
110
+ export async function closeBrowser(session) {
111
+ try {
112
+ await session.client.close();
113
+ }
114
+ catch (error) {
115
+ // Ignore close errors
116
+ }
117
+ try {
118
+ session.chromeProcess.kill();
119
+ }
120
+ catch (error) {
121
+ // Ignore kill errors
122
+ }
123
+ }
124
+ /**
125
+ * Navigate to URL
126
+ */
127
+ export async function navigate(session, url) {
128
+ await session.Page.navigate({ url });
129
+ await session.Page.loadEventFired();
130
+ }
131
+ /**
132
+ * Get page title
133
+ */
134
+ export async function getTitle(session) {
135
+ const result = await session.Runtime.evaluate({ expression: 'document.title' });
136
+ return result.result.value;
137
+ }
138
+ /**
139
+ * Get current URL
140
+ */
141
+ export async function getUrl(session) {
142
+ const result = await session.Runtime.evaluate({ expression: 'window.location.href' });
143
+ return result.result.value;
144
+ }
145
+ /**
146
+ * Wait for element to be available
147
+ */
148
+ export async function waitForSelector(session, selector, timeout = 5000) {
149
+ const startTime = Date.now();
150
+ const checkInterval = 100;
151
+ while (Date.now() - startTime < timeout) {
152
+ try {
153
+ const nodeId = await querySelector(session, selector);
154
+ if (nodeId !== null) {
155
+ return nodeId;
156
+ }
157
+ }
158
+ catch (error) {
159
+ // Element not found, continue waiting
160
+ }
161
+ await setTimeout(checkInterval);
162
+ }
163
+ throw new Error(`Element "${selector}" not found within ${timeout}ms`);
164
+ }
165
+ /**
166
+ * Query selector and return nodeId
167
+ */
168
+ export async function querySelector(session, selector) {
169
+ const document = await session.DOM.getDocument();
170
+ const { nodeId } = await session.DOM.querySelector({
171
+ nodeId: document.root.nodeId,
172
+ selector: selector
173
+ });
174
+ return nodeId;
175
+ }
176
+ /**
177
+ * Click an element
178
+ */
179
+ export async function clickElement(session, selector) {
180
+ const nodeId = await waitForSelector(session, selector);
181
+ // Get bounding box
182
+ const boxModel = await session.DOM.getBoxModel({ nodeId });
183
+ if (!boxModel.model) {
184
+ throw new Error(`Could not get bounding box for selector: ${selector}`);
185
+ }
186
+ const content = boxModel.model.content;
187
+ const x = (content[0] + content[2]) / 2; // Center X
188
+ const y = (content[1] + content[5]) / 2; // Center Y
189
+ // Click at coordinates
190
+ await session.Input.dispatchMouseEvent({
191
+ type: 'mousePressed',
192
+ x: x,
193
+ y: y,
194
+ button: 'left',
195
+ clickCount: 1
196
+ });
197
+ await session.Input.dispatchMouseEvent({
198
+ type: 'mouseReleased',
199
+ x: x,
200
+ y: y,
201
+ button: 'left',
202
+ clickCount: 1
203
+ });
204
+ // Wait a bit for any navigation or updates
205
+ await setTimeout(100);
206
+ }
207
+ /**
208
+ * Type text into an input field
209
+ */
210
+ export async function typeText(session, selector, text) {
211
+ const nodeId = await waitForSelector(session, selector);
212
+ // Focus the element
213
+ await session.DOM.focus({ nodeId });
214
+ // Clear existing value using nodeId
215
+ const result = await session.DOM.resolveNode({ nodeId });
216
+ if (result.object.objectId) {
217
+ await session.Runtime.callFunctionOn({
218
+ objectId: result.object.objectId,
219
+ functionDeclaration: 'function() { this.value = ""; this.focus(); }'
220
+ });
221
+ }
222
+ // Type character by character
223
+ for (const char of text) {
224
+ await session.Input.dispatchKeyEvent({
225
+ type: 'char',
226
+ text: char
227
+ });
228
+ await setTimeout(10); // Small delay between keystrokes
229
+ }
230
+ }
231
+ /**
232
+ * Get text content of an element
233
+ */
234
+ export async function getTextContent(session, selector) {
235
+ const nodeId = await waitForSelector(session, selector);
236
+ const result = await session.DOM.resolveNode({ nodeId });
237
+ if (result.object.objectId) {
238
+ const textResult = await session.Runtime.callFunctionOn({
239
+ objectId: result.object.objectId,
240
+ functionDeclaration: 'function() { return this.textContent || this.innerText || ""; }',
241
+ returnByValue: true
242
+ });
243
+ return textResult.result.value || '';
244
+ }
245
+ return '';
246
+ }
247
+ /**
248
+ * Check if element is visible
249
+ */
250
+ export async function isVisible(session, selector) {
251
+ try {
252
+ const nodeId = await querySelector(session, selector);
253
+ if (nodeId === null)
254
+ return false;
255
+ const result = await session.DOM.resolveNode({ nodeId });
256
+ if (!result.object.objectId)
257
+ return false;
258
+ const visibilityResult = await session.Runtime.callFunctionOn({
259
+ objectId: result.object.objectId,
260
+ functionDeclaration: `
261
+ function() {
262
+ const style = window.getComputedStyle(this);
263
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
264
+ }
265
+ `,
266
+ returnByValue: true
267
+ });
268
+ return visibilityResult.result.value === true;
269
+ }
270
+ catch (error) {
271
+ return false;
272
+ }
273
+ }
274
+ /**
275
+ * Wait for specified seconds
276
+ */
277
+ export async function wait(seconds) {
278
+ await setTimeout(seconds * 1000);
279
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Command Executor - Executes Assure commands
3
+ */
4
+ import { executeOpen } from '../commands/open.js';
5
+ import { executeClick } from '../commands/click.js';
6
+ import { executeType } from '../commands/type.js';
7
+ import { executeExpect } from '../commands/expect.js';
8
+ import { wait } from './browser.js';
9
+ export async function execute(commands, session) {
10
+ for (const cmd of commands) {
11
+ const { action, args, lineNumber } = cmd;
12
+ try {
13
+ switch (action) {
14
+ case 'OPEN':
15
+ await executeOpen(session, args);
16
+ console.log(`โœ“ Line ${lineNumber}: OPEN "${args[0]}"`);
17
+ break;
18
+ case 'CLICK':
19
+ await executeClick(session, args);
20
+ console.log(`โœ“ Line ${lineNumber}: CLICK "${args[0]}"`);
21
+ break;
22
+ case 'TYPE':
23
+ await executeType(session, args);
24
+ console.log(`โœ“ Line ${lineNumber}: TYPE "${args[0]}" "${args.slice(1).join(' ')}"`);
25
+ break;
26
+ case 'WAIT':
27
+ const seconds = Number(args[0]);
28
+ if (isNaN(seconds)) {
29
+ throw new Error(`WAIT command requires a valid number, got: ${args[0]}`);
30
+ }
31
+ await wait(seconds);
32
+ console.log(`โœ“ Line ${lineNumber}: WAIT ${seconds}`);
33
+ break;
34
+ case 'EXPECT':
35
+ await executeExpect(session, args);
36
+ console.log(`โœ“ Line ${lineNumber}: EXPECT ${args.join(' ')}`);
37
+ break;
38
+ case 'TEST':
39
+ // TEST is just a label, skip execution
40
+ console.log(`\n๐Ÿงช ${args.join(' ')}`);
41
+ break;
42
+ default:
43
+ throw new Error(`Unknown command: ${action} at line ${lineNumber}`);
44
+ }
45
+ }
46
+ catch (error) {
47
+ console.error(`\nโŒ Error at line ${lineNumber}: ${action} ${args.join(' ')}`);
48
+ console.error(` ${error.message}`);
49
+ throw error;
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Parser - Parses Assure test scripts into command arrays
3
+ */
4
+ import { tokenize } from './tokenizer.js';
5
+ export function parse(script) {
6
+ const lines = script.split('\n');
7
+ const commands = [];
8
+ for (let i = 0; i < lines.length; i++) {
9
+ const line = lines[i].trim();
10
+ // Skip empty lines and comments
11
+ if (!line || line.startsWith('#')) {
12
+ continue;
13
+ }
14
+ const tokens = tokenize(line);
15
+ if (tokens.length === 0) {
16
+ continue;
17
+ }
18
+ const [action, ...args] = tokens;
19
+ commands.push({
20
+ action: action.toUpperCase(),
21
+ args: args.map(arg => arg.replace(/^["']|["']$/g, '')), // Remove quotes
22
+ lineNumber: i + 1
23
+ });
24
+ }
25
+ return commands;
26
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Tokenizer - Breaks lines into tokens
3
+ * Handles quoted strings and whitespace-separated tokens
4
+ */
5
+ export function tokenize(line) {
6
+ const tokens = [];
7
+ let current = '';
8
+ let inQuotes = false;
9
+ let quoteChar = '';
10
+ for (let i = 0; i < line.length; i++) {
11
+ const char = line[i];
12
+ if ((char === '"' || char === "'") && !inQuotes) {
13
+ inQuotes = true;
14
+ quoteChar = char;
15
+ continue;
16
+ }
17
+ if (char === quoteChar && inQuotes) {
18
+ inQuotes = false;
19
+ if (current.trim()) {
20
+ tokens.push(current.trim());
21
+ current = '';
22
+ }
23
+ continue;
24
+ }
25
+ if (inQuotes) {
26
+ current += char;
27
+ }
28
+ else if (char === ' ' || char === '\t') {
29
+ if (current.trim()) {
30
+ tokens.push(current.trim());
31
+ current = '';
32
+ }
33
+ }
34
+ else {
35
+ current += char;
36
+ }
37
+ }
38
+ if (current.trim()) {
39
+ tokens.push(current.trim());
40
+ }
41
+ return tokens;
42
+ }
package/dist/runner.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Assure Test Runner
3
+ * Runs .assure test files using custom CDP-based engine
4
+ */
5
+ import fs from 'fs';
6
+ import { parse } from './language/parser.js';
7
+ import { createBrowser, closeBrowser } from './engine/browser.js';
8
+ import { execute } from './engine/executor.js';
9
+ async function runTest(testFile) {
10
+ console.log(`\n๐Ÿš€ Assure Test Runner`);
11
+ console.log(`๐Ÿ“„ Running: ${testFile}\n`);
12
+ // Validate file extension
13
+ if (!testFile.endsWith('.assure')) {
14
+ console.error(`โŒ Error: Test files must have .assure extension`);
15
+ console.error(` Received: ${testFile}`);
16
+ console.error(` Expected: *.assure`);
17
+ process.exit(1);
18
+ }
19
+ // Read test file
20
+ if (!fs.existsSync(testFile)) {
21
+ console.error(`โŒ Test file not found: ${testFile}`);
22
+ console.error(` Make sure the file exists and has the .assure extension`);
23
+ process.exit(1);
24
+ }
25
+ const script = fs.readFileSync(testFile, 'utf-8');
26
+ const commands = parse(script);
27
+ if (commands.length === 0) {
28
+ console.error('โŒ No commands found in test file');
29
+ process.exit(1);
30
+ }
31
+ let session;
32
+ try {
33
+ // Create browser session
34
+ console.log('๐ŸŒ Launching browser...');
35
+ session = await createBrowser(true); // headless mode
36
+ console.log('โœ“ Browser launched\n');
37
+ // Execute commands
38
+ await execute(commands, session);
39
+ console.log('\nโœ… TEST COMPLETED SUCCESSFULLY');
40
+ }
41
+ catch (error) {
42
+ console.error(`\nโŒ TEST FAILED: ${error.message}`);
43
+ process.exit(1);
44
+ }
45
+ finally {
46
+ // Cleanup
47
+ if (session) {
48
+ console.log('\n๐Ÿงน Cleaning up...');
49
+ await closeBrowser(session);
50
+ console.log('โœ“ Browser closed');
51
+ }
52
+ }
53
+ }
54
+ // Main entry point
55
+ const testFile = process.argv[2];
56
+ if (!testFile) {
57
+ console.log(`
58
+ ๐Ÿงช Assure Testing Language
59
+
60
+ Usage:
61
+ assure <test-file.assure>
62
+
63
+ Examples:
64
+ assure login.assure
65
+ assure tests/checkout.assure
66
+
67
+ For more information, visit: https://github.com/yourusername/assure
68
+ `);
69
+ process.exit(0);
70
+ }
71
+ runTest(testFile).catch((error) => {
72
+ console.error('Fatal error:', error);
73
+ process.exit(1);
74
+ });
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "assure-testing",
3
+ "version": "1.0.0",
4
+ "description": "Assure - A custom testing language (DSL) for browser automation. Test files use .assure extension.",
5
+ "type": "module",
6
+ "main": "dist/runner.js",
7
+ "bin": {
8
+ "assure": "./dist/runner.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "test": "tsx runner.ts",
17
+ "run": "tsx runner.ts",
18
+ "build": "tsc",
19
+ "prepublishOnly": "npm run build",
20
+ "start": "node dist/runner.js"
21
+ },
22
+ "keywords": [
23
+ "testing",
24
+ "test",
25
+ "dsl",
26
+ "browser-automation",
27
+ "cdp",
28
+ "custom-engine",
29
+ "e2e",
30
+ "end-to-end",
31
+ "automation",
32
+ "chrome-devtools-protocol",
33
+ "assure"
34
+ ],
35
+ "author": "",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/yourusername/assure.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/yourusername/assure/issues"
43
+ },
44
+ "homepage": "https://github.com/yourusername/assure#readme",
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ },
48
+ "dependencies": {
49
+ "chrome-remote-interface": "^0.33.2"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.0.0",
53
+ "tsx": "^4.7.0",
54
+ "typescript": "^5.3.0"
55
+ }
56
+ }
57
+