@flash-ai-team/flash-test-framework 0.0.1

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,110 @@
1
+ # Flash Test Framework
2
+
3
+ A powerful, keyword-driven automation framework built on top of Playwright and TypeScript, featuring AI-powered capabilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install flash-test-framework
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ 1. **Initialize Playwright** (if not already done):
14
+ ```bash
15
+ npx playwright install
16
+ ```
17
+
18
+ 2. **Configuration**:
19
+ * **Email Reporting**: Create `email.config.json` in your project root if you want email reports.
20
+ * **AI Features**: Create `ai.config.json` with your OpenAI API key to use `AIWebUI`.
21
+ ```json
22
+ {
23
+ "enabled": true,
24
+ "provider": "openai",
25
+ "apiKey": "sk-...",
26
+ "model": "gpt-4o"
27
+ }
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### 1. Basic Web Automation (`web`)
33
+
34
+ Use `web` for standard, deterministic interactions using selectors.
35
+
36
+ ```typescript
37
+ import { test } from '@playwright/test';
38
+ import { web, findTestObject, KeywordContext } from 'flash-test-framework';
39
+
40
+ test.describe('My Test Suite', () => {
41
+ test.beforeEach(async ({ page }, testInfo) => {
42
+ // Initialize Context
43
+ KeywordContext.page = page;
44
+ KeywordContext.testInfo = testInfo;
45
+ });
46
+
47
+ test('Login Test', async () => {
48
+ await web.navigateToUrl('https://example.com/login');
49
+
50
+ const usernameInput = findTestObject('#username', 'Username Field');
51
+ const passwordInput = findTestObject('#password', 'Password Field');
52
+ const loginButton = findTestObject('button[type="submit"]', 'Login Button');
53
+
54
+ await web.setText(usernameInput, 'myuser');
55
+ await web.setText(passwordInput, 'mypassword');
56
+ await web.click(loginButton);
57
+
58
+ // precise search using heuristics
59
+ await web.search("Specific Item");
60
+ });
61
+ });
62
+ ```
63
+
64
+ ### 2. AI & Manual Steps (`aiWeb`)
65
+
66
+ Use `aiWeb` to write tests in plain English or to click elements that are hard to target (like map markers) using images.
67
+
68
+ **Example: `AIManualSteps.spec.ts`**
69
+
70
+ ```typescript
71
+ import { test } from '@playwright/test';
72
+ import { aiWeb, KeywordContext } from 'flash-test-framework';
73
+
74
+ test.describe('AI Scenario', () => {
75
+ test.beforeEach(async ({ page }, testInfo) => {
76
+ KeywordContext.page = page;
77
+ KeywordContext.testInfo = testInfo;
78
+ });
79
+
80
+ test('Google Maps Flow', async () => {
81
+ await aiWeb.executeManualSteps(`
82
+ 1. Navigate to "https://www.google.com/maps"
83
+ 2. Click on "Accept all"
84
+ 3. Search "KFC"
85
+ 4. Press Enter
86
+ 5. Click on the marker image
87
+ `);
88
+ });
89
+ });
90
+ ```
91
+
92
+ **Key Features:**
93
+ * **Natural Language Parsing**: "Click", "Navigate", "Search", "Press Key".
94
+ * **Image Matching**: Place an image (e.g., `marker.png`) in `tests/assets/`. If you write `Click on "marker image"`, the framework will look for `tests/assets/marker.png` and click it on the screen.
95
+ * **Heuristic Search**: `Search "text"` automatically finds search bars.
96
+
97
+ ## Keywords
98
+
99
+ ### web
100
+ * `navigateToUrl(url)`
101
+ * `click(testObject)`
102
+ * `setText(testObject, text)`
103
+ * `search(text)` - Smart search input targeting.
104
+ * `clickImage(imagePath)` - Visual testing.
105
+ * `pressKey(key)` - Keyboard interactions.
106
+ * `verifyElementPresent(testObject)`
107
+ * ...and more.
108
+
109
+ ### aiWeb
110
+ * `executeManualSteps(steps: string)` - Parses multi-line natural language steps.
@@ -0,0 +1,19 @@
1
+ import { Page, TestInfo } from '@playwright/test';
2
+ export declare const KeywordRegistry: Record<string, Function>;
3
+ /**
4
+ * Decorator to register a function as a Keyword and wrap it in a Playwright Step
5
+ * Supports both Stage 3 (Standard) and Stage 2 (Legacy/Experimental) decorators.
6
+ * @param name Custom name for the keyword (optional)
7
+ */
8
+ export declare function Keyword(name?: string): (targetOrValue: any, contextOrKey: any, descriptor?: PropertyDescriptor) => any;
9
+ /**
10
+ * Context holder to share Page and TestInfo across keywords
11
+ */
12
+ export declare class KeywordContext {
13
+ private static _page;
14
+ private static _testInfo;
15
+ static set page(p: Page);
16
+ static get page(): Page;
17
+ static set testInfo(t: TestInfo);
18
+ static get testInfo(): TestInfo;
19
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeywordContext = exports.KeywordRegistry = void 0;
4
+ exports.Keyword = Keyword;
5
+ const test_1 = require("@playwright/test");
6
+ const ObjectRepository_1 = require("./ObjectRepository");
7
+ // Registry to store keyword metadata
8
+ exports.KeywordRegistry = {};
9
+ /**
10
+ * Helper to format arguments for display in reports
11
+ */
12
+ function formatArg(arg) {
13
+ if (arg === null)
14
+ return 'null';
15
+ if (arg === undefined)
16
+ return 'undefined';
17
+ if (typeof arg === 'string')
18
+ return `"${arg}"`;
19
+ if (typeof arg === 'number' || typeof arg === 'boolean')
20
+ return String(arg);
21
+ if (arg instanceof ObjectRepository_1.TestObject) {
22
+ return `[Object: ${arg.description || arg.selector}]`;
23
+ }
24
+ try {
25
+ return JSON.stringify(arg);
26
+ }
27
+ catch {
28
+ return String(arg);
29
+ }
30
+ }
31
+ /**
32
+ * Decorator to register a function as a Keyword and wrap it in a Playwright Step
33
+ * Supports both Stage 3 (Standard) and Stage 2 (Legacy/Experimental) decorators.
34
+ * @param name Custom name for the keyword (optional)
35
+ */
36
+ function Keyword(name) {
37
+ return function (targetOrValue, contextOrKey, descriptor) {
38
+ // Stage 3: (value, context) where context is an object
39
+ if (typeof contextOrKey === 'object' && contextOrKey !== null && 'kind' in contextOrKey) {
40
+ const originalMethod = targetOrValue;
41
+ const context = contextOrKey;
42
+ const keywordName = name || String(context.name);
43
+ // Registry
44
+ exports.KeywordRegistry[keywordName] = originalMethod;
45
+ return async function (...args) {
46
+ const argString = args.map(formatArg).join(', ');
47
+ const stepName = argString ? `${keywordName} (${argString})` : keywordName;
48
+ return await test_1.test.step(stepName, async () => {
49
+ return await originalMethod.apply(this, args);
50
+ });
51
+ };
52
+ }
53
+ // Legacy: (target, propertyKey, descriptor)
54
+ // Note: For static methods in legacy, target is the Constructor.
55
+ let legacyDescriptor = descriptor;
56
+ if (!legacyDescriptor && typeof contextOrKey === 'string') {
57
+ legacyDescriptor = Object.getOwnPropertyDescriptor(targetOrValue, contextOrKey);
58
+ }
59
+ if (legacyDescriptor) {
60
+ const originalMethod = legacyDescriptor.value;
61
+ const keywordName = name || contextOrKey;
62
+ legacyDescriptor.value = async function (...args) {
63
+ const argString = args.map(formatArg).join(', ');
64
+ const stepName = argString ? `${keywordName} (${argString})` : keywordName;
65
+ return await test_1.test.step(stepName, async () => {
66
+ return await originalMethod.apply(this, args);
67
+ });
68
+ };
69
+ exports.KeywordRegistry[keywordName] = legacyDescriptor.value;
70
+ return legacyDescriptor;
71
+ }
72
+ console.warn(`@Keyword decorator failed to apply for ${name || 'unknown'}`);
73
+ };
74
+ }
75
+ /**
76
+ * Context holder to share Page and TestInfo across keywords
77
+ */
78
+ class KeywordContext {
79
+ static set page(p) {
80
+ this._page = p;
81
+ }
82
+ static get page() {
83
+ if (!this._page) {
84
+ throw new Error("Page not initialized. Ensure 'TestBase' setup is called.");
85
+ }
86
+ return this._page;
87
+ }
88
+ static set testInfo(t) {
89
+ this._testInfo = t;
90
+ }
91
+ static get testInfo() {
92
+ return this._testInfo;
93
+ }
94
+ }
95
+ exports.KeywordContext = KeywordContext;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Represents a Test Object wrapper around Playwright Locators
3
+ */
4
+ export declare class TestObject {
5
+ selector: string;
6
+ description: string;
7
+ constructor(selector: string, description?: string);
8
+ }
9
+ /**
10
+ * Helper to create Test Objects easily
11
+ */
12
+ export declare function el(selector: string, description?: string): TestObject;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestObject = void 0;
4
+ exports.el = el;
5
+ /**
6
+ * Represents a Test Object wrapper around Playwright Locators
7
+ */
8
+ class TestObject {
9
+ constructor(selector, description = "") {
10
+ this.selector = selector;
11
+ this.description = description;
12
+ }
13
+ }
14
+ exports.TestObject = TestObject;
15
+ /**
16
+ * Helper to create Test Objects easily
17
+ */
18
+ function el(selector, description) {
19
+ return new TestObject(selector, description);
20
+ }
@@ -0,0 +1,5 @@
1
+ type FrameworkFixtures = {
2
+ contextSetup: void;
3
+ };
4
+ export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & FrameworkFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
5
+ export { expect } from '@playwright/test';
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.expect = exports.test = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ const Keyword_1 = require("./Keyword");
6
+ exports.test = test_1.test.extend({
7
+ contextSetup: [async ({ page }, use, testInfo) => {
8
+ // Initialize the Keyword Context before every test
9
+ Keyword_1.KeywordContext.page = page;
10
+ Keyword_1.KeywordContext.testInfo = testInfo;
11
+ await use();
12
+ }, { auto: true }],
13
+ });
14
+ var test_2 = require("@playwright/test");
15
+ Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_2.expect; } });
@@ -0,0 +1,2 @@
1
+ export declare function TestCase(target: any, propertyKey: string, descriptor: PropertyDescriptor): void | PropertyDescriptor;
2
+ export declare function TestCase(value: Function, context: ClassMethodDecoratorContext): Function;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestCase = TestCase;
4
+ const test_1 = require("@playwright/test");
5
+ /**
6
+ * Decorator to register a method as a Test Case.
7
+ * Automatically adds the MethodName annotation to the test info.
8
+ *
9
+ * Usage:
10
+ * class MyTests {
11
+ * @TestCase()
12
+ * static async MyTest() { ... }
13
+ * }
14
+ */
15
+ /**
16
+ * Decorator to register a method as a Test Case.
17
+ * Automatically adds the MethodName annotation to the test info.
18
+ *
19
+ * Usage:
20
+ * class MyTests {
21
+ * @TestCase
22
+ * static async MyTest() { ... }
23
+ * }
24
+ */
25
+ /**
26
+ * Helper to extract the caller file from the stack trace
27
+ */
28
+ function getCallerFile() {
29
+ const originalPrepare = Error.prepareStackTrace;
30
+ Error.prepareStackTrace = (_, stack) => stack;
31
+ const err = new Error();
32
+ const stack = err.stack;
33
+ Error.prepareStackTrace = originalPrepare;
34
+ if (!stack)
35
+ return '';
36
+ const currentFile = __filename;
37
+ // Find the first frame that is NOT this file
38
+ for (const frame of stack) {
39
+ const fileName = frame.getFileName();
40
+ if (fileName && fileName !== currentFile && !fileName.includes('node_modules') && !fileName.includes('internal/')) {
41
+ return fileName;
42
+ }
43
+ }
44
+ return '';
45
+ }
46
+ function TestCase(targetOrValue, contextOrKey, descriptor) {
47
+ // Stage 3: (value, context)
48
+ if (typeof contextOrKey === 'object' && contextOrKey !== null && 'kind' in contextOrKey) {
49
+ const originalMethod = targetOrValue;
50
+ const context = contextOrKey;
51
+ const methodName = String(context.name);
52
+ // Capture at definition time
53
+ const definitionFile = getCallerFile();
54
+ return async function (...args) {
55
+ test_1.test.info().annotations.push({ type: 'MethodName', description: methodName });
56
+ if (definitionFile) {
57
+ test_1.test.info().annotations.push({ type: 'SourceFile', description: definitionFile });
58
+ }
59
+ return await originalMethod.apply(this, args);
60
+ };
61
+ }
62
+ // Legacy: (target, propertyKey, descriptor)
63
+ let legacyDescriptor = descriptor;
64
+ if (!legacyDescriptor && typeof contextOrKey === 'string') {
65
+ legacyDescriptor = Object.getOwnPropertyDescriptor(targetOrValue, contextOrKey);
66
+ }
67
+ if (legacyDescriptor) {
68
+ const originalMethod = legacyDescriptor.value;
69
+ const methodName = contextOrKey;
70
+ // Capture at definition time
71
+ const definitionFile = getCallerFile();
72
+ legacyDescriptor.value = async function (...args) {
73
+ test_1.test.info().annotations.push({ type: 'MethodName', description: methodName });
74
+ if (definitionFile) {
75
+ test_1.test.info().annotations.push({ type: 'SourceFile', description: definitionFile });
76
+ }
77
+ return await originalMethod.apply(this, args);
78
+ };
79
+ return legacyDescriptor;
80
+ }
81
+ }
@@ -0,0 +1,4 @@
1
+ export { Web } from './keywords/WebUI';
2
+ export { AIWeb } from './keywords/AIWebUI';
3
+ export { Keyword, KeywordContext } from './core/Keyword';
4
+ export { TestObject, el } from './core/ObjectRepository';
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.el = exports.TestObject = exports.KeywordContext = exports.Keyword = exports.AIWeb = exports.Web = void 0;
4
+ var WebUI_1 = require("./keywords/WebUI");
5
+ Object.defineProperty(exports, "Web", { enumerable: true, get: function () { return WebUI_1.Web; } });
6
+ var AIWebUI_1 = require("./keywords/AIWebUI");
7
+ Object.defineProperty(exports, "AIWeb", { enumerable: true, get: function () { return AIWebUI_1.AIWeb; } });
8
+ var Keyword_1 = require("./core/Keyword");
9
+ Object.defineProperty(exports, "Keyword", { enumerable: true, get: function () { return Keyword_1.Keyword; } });
10
+ Object.defineProperty(exports, "KeywordContext", { enumerable: true, get: function () { return Keyword_1.KeywordContext; } });
11
+ var ObjectRepository_1 = require("./core/ObjectRepository");
12
+ Object.defineProperty(exports, "TestObject", { enumerable: true, get: function () { return ObjectRepository_1.TestObject; } });
13
+ Object.defineProperty(exports, "el", { enumerable: true, get: function () { return ObjectRepository_1.el; } });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * AIWeb: Uses LLM to locate elements based on natural language descriptions.
3
+ */
4
+ export declare class AIWeb {
5
+ private static get page();
6
+ private static get config();
7
+ private static getOpenAIClient;
8
+ /**
9
+ * Finds a locator using AI based on a natural language description.
10
+ */
11
+ private static findElement;
12
+ static click(description: string): Promise<void>;
13
+ static setText(description: string, text: string): Promise<void>;
14
+ static verifyText(description: string, expectedText: string): Promise<void>;
15
+ /**
16
+ * Executes manual test steps by converting them to WebUI keyword calls using AI.
17
+ * @param steps - Natural language test steps (one per line or paragraph).
18
+ */
19
+ static executeManualSteps(steps: string): Promise<void>;
20
+ }