agentq-webdriverio 1.0.2 → 1.0.3
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 +113 -1
- package/dist/index.js +2 -1
- package/dist/pull-testcase.js +49 -15
- package/dist/testResult.d.ts +1 -0
- package/dist/testResult.js +38 -5
- package/package.json +1 -1
- package/agentq.config.json +0 -3
package/README.md
CHANGED
|
@@ -1 +1,113 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AgentQ for WebdriverIO
|
|
2
|
+
|
|
3
|
+
Integrate WebdriverIO (TypeScript) with AgentQ AI for AI-driven automation and automated test-result reporting.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install agentq-webdriverio
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
### 1. AgentQ config (AI steps)
|
|
14
|
+
|
|
15
|
+
Create `agentq.config.json` in your project root with your company API key (from your account profile; you must sign in first):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"TOKEN": "YOUR_API_KEY"
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or export it as an environment variable:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
export AGENTQ_TOKEN="YOUR_API_KEY"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Environment variables (result reporting)
|
|
30
|
+
|
|
31
|
+
Set these in `.env` or your CI environment. The recommended way is the company API key — the same key as above, no email/password needed:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
AGENTQ_API_KEY=your_company_api_key # from your AgentQ profile
|
|
35
|
+
AGENTQ_PROJECT_ID=your_project_id
|
|
36
|
+
AGENTQ_TESTRUN_ID=your_testrun_id
|
|
37
|
+
AGENTQ_EMAIL=your_email # optional: results show this member as "Created By" (owner or invited member of the key's company)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Email/password login is still supported as a fallback (when no API key is set):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
AGENTQ_EMAIL=your_email
|
|
44
|
+
AGENTQ_PASSWORD=your_password
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 3. WebdriverIO hooks (`wdio.conf.ts`)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { initAgentQ, handleTestConclusion } from 'agentq-webdriverio';
|
|
51
|
+
|
|
52
|
+
export const config: WebdriverIO.Config = {
|
|
53
|
+
// ... other config
|
|
54
|
+
|
|
55
|
+
before: function () {
|
|
56
|
+
initAgentQ(browser);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
afterTest: async function (test, context, { error, duration, passed }) {
|
|
60
|
+
await handleTestConclusion(
|
|
61
|
+
test.title,
|
|
62
|
+
passed ? 'passed' : 'failed',
|
|
63
|
+
Date.now() - duration,
|
|
64
|
+
error?.message
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
Prefix test titles with the AgentQ test case ID (e.g. `17-`) — results are matched to test cases by that numeric prefix:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { q, test } from 'agentq-webdriverio';
|
|
76
|
+
|
|
77
|
+
describe('AI Automation Flow', () => {
|
|
78
|
+
test('17-should login using AI instructions', async () => {
|
|
79
|
+
await browser.url('https://the-internet.herokuapp.com/login');
|
|
80
|
+
|
|
81
|
+
await q('user fill username tomsmith');
|
|
82
|
+
await q('user fill password SuperSecretPassword!');
|
|
83
|
+
await q('user click login button');
|
|
84
|
+
|
|
85
|
+
const flashAlert = await $('#flash');
|
|
86
|
+
await expect(flashAlert).toHaveText(
|
|
87
|
+
expect.stringContaining('You logged into a secure area!')
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Test Run Sync (pull test suite & push results)
|
|
94
|
+
|
|
95
|
+
Pull a test suite from a test run and report each test's status back to AgentQ:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx agentq-pull-testcase -- --tcid=YOUR_TC_ID
|
|
99
|
+
npx agentq-pull-testsuite -- --testrunid=YOUR_TESTRUN_ID
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Then run the generated specs:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx wdio run ./wdio.conf.ts
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> Tip for CI (e.g. GitHub Actions): store `AGENTQ_API_KEY` as a secret and export it in the workflow. API key auth skips the login endpoint entirely.
|
|
109
|
+
|
|
110
|
+
## Plans and Rate Limits
|
|
111
|
+
|
|
112
|
+
- Free: Free of charge
|
|
113
|
+
- Pro & Enterprise: visit [www.agentq.id](https://www.agentq.id) or contact support@agentq.id to upgrade your plan.
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,8 @@ Object.defineProperty(exports, "uploadArtifact", { enumerable: true, get: functi
|
|
|
29
29
|
async function handleTestConclusion(testTitle, status, startTime, errorDetails) {
|
|
30
30
|
const tcId = parseInt(testTitle.split('-')[0]);
|
|
31
31
|
if (!isNaN(tcId) && process.env.AGENTQ_TESTRUN_ID) {
|
|
32
|
-
|
|
32
|
+
// With AGENTQ_API_KEY set, requests use X-API-Key and no login is needed.
|
|
33
|
+
if (!process.env.AGENTQ_API_KEY && !cachedAccessToken) {
|
|
33
34
|
cachedAccessToken = await (0, testResult_2.getAccessToken)();
|
|
34
35
|
}
|
|
35
36
|
const executionTime = (Date.now() - startTime) / 1000;
|
package/dist/pull-testcase.js
CHANGED
|
@@ -14,6 +14,7 @@ const config = {
|
|
|
14
14
|
apiBaseUrl: process.env.AGENTQ_API_URL || 'https://backend-app.agentq.id',
|
|
15
15
|
projectId: `${process.env.AGENTQ_PROJECT_ID}`,
|
|
16
16
|
testRunId: `${process.env.AGENTQ_TESTRUN_ID}`,
|
|
17
|
+
apiKey: process.env.AGENTQ_API_KEY || null,
|
|
17
18
|
authEndpoint: '/auth/login',
|
|
18
19
|
authData: {
|
|
19
20
|
email: `${process.env.AGENTQ_EMAIL}`,
|
|
@@ -21,6 +22,33 @@ const config = {
|
|
|
21
22
|
},
|
|
22
23
|
outputDir: path_1.default.join(process.cwd(), 'tests')
|
|
23
24
|
};
|
|
25
|
+
// Identify ourselves instead of the default axios/x.y UA, which bot
|
|
26
|
+
// protections (e.g. Cloudflare) commonly flag in CI environments.
|
|
27
|
+
let pkgVersion = '';
|
|
28
|
+
try {
|
|
29
|
+
pkgVersion = require('../package.json').version;
|
|
30
|
+
}
|
|
31
|
+
catch { /* best effort */ }
|
|
32
|
+
const USER_AGENT = `agentq-webdriverio${pkgVersion ? `/${pkgVersion}` : ''}`;
|
|
33
|
+
// API key auth (X-API-Key) when AGENTQ_API_KEY is set, otherwise the
|
|
34
|
+
// Bearer token obtained from email/password login.
|
|
35
|
+
function authHeaders(accessToken) {
|
|
36
|
+
const headers = {
|
|
37
|
+
'accept': 'application/json',
|
|
38
|
+
'User-Agent': USER_AGENT
|
|
39
|
+
};
|
|
40
|
+
if (config.apiKey) {
|
|
41
|
+
headers['X-API-Key'] = config.apiKey;
|
|
42
|
+
// Optional attribution: see testResult.ts getAuthHeaders.
|
|
43
|
+
if (process.env.AGENTQ_EMAIL) {
|
|
44
|
+
headers['X-Actor-Email'] = process.env.AGENTQ_EMAIL;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (accessToken) {
|
|
48
|
+
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
49
|
+
}
|
|
50
|
+
return headers;
|
|
51
|
+
}
|
|
24
52
|
// Parse command line arguments more robustly
|
|
25
53
|
function parseArgs() {
|
|
26
54
|
const args = process.argv.slice(2);
|
|
@@ -63,12 +91,18 @@ if (!fs_1.default.existsSync(config.outputDir)) {
|
|
|
63
91
|
fs_1.default.mkdirSync(config.outputDir, { recursive: true });
|
|
64
92
|
}
|
|
65
93
|
async function getAccessToken() {
|
|
94
|
+
if (!process.env.AGENTQ_EMAIL || !process.env.AGENTQ_PASSWORD) {
|
|
95
|
+
console.error('Error: No credentials provided.');
|
|
96
|
+
console.error('Set AGENTQ_API_KEY (recommended), or AGENTQ_EMAIL and AGENTQ_PASSWORD.');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
66
99
|
try {
|
|
67
100
|
console.log(`Authenticating with: ${config.apiBaseUrl}${config.authEndpoint}`);
|
|
68
101
|
const response = await axios_1.default.post(`${config.apiBaseUrl}${config.authEndpoint}`, config.authData, {
|
|
69
102
|
headers: {
|
|
70
103
|
'accept': 'application/json',
|
|
71
|
-
'Content-Type': 'application/json'
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'User-Agent': USER_AGENT
|
|
72
106
|
}
|
|
73
107
|
});
|
|
74
108
|
return response.data.access_token;
|
|
@@ -92,10 +126,7 @@ async function fetchTestCase(tcId, accessToken) {
|
|
|
92
126
|
try {
|
|
93
127
|
console.log(`Fetching test case from: ${config.apiBaseUrl}/projects/${config.projectId}/test-cases/tcId/${tcId}`);
|
|
94
128
|
const response = await axios_1.default.get(`${config.apiBaseUrl}/projects/${config.projectId}/test-cases/tcId/${tcId}`, {
|
|
95
|
-
headers:
|
|
96
|
-
'accept': 'application/json',
|
|
97
|
-
'Authorization': `Bearer ${accessToken}`
|
|
98
|
-
}
|
|
129
|
+
headers: authHeaders(accessToken)
|
|
99
130
|
});
|
|
100
131
|
return response.data;
|
|
101
132
|
}
|
|
@@ -119,10 +150,7 @@ async function fetchTestSuite(testRunId, accessToken) {
|
|
|
119
150
|
const apiUrl = `${config.apiBaseUrl}/projects/${config.projectId}/test-runs/${testRunId}/test-results?page=1&limit=10000`;
|
|
120
151
|
console.log(`Fetching test suite results from: ${apiUrl}`);
|
|
121
152
|
const response = await axios_1.default.get(apiUrl, {
|
|
122
|
-
headers:
|
|
123
|
-
'accept': 'application/json',
|
|
124
|
-
'Authorization': `Bearer ${accessToken}`
|
|
125
|
-
}
|
|
153
|
+
headers: authHeaders(accessToken)
|
|
126
154
|
});
|
|
127
155
|
return response.data;
|
|
128
156
|
}
|
|
@@ -225,13 +253,21 @@ ${expectationCode}
|
|
|
225
253
|
console.log(`✅ Created: ${filePath}`);
|
|
226
254
|
return filePath;
|
|
227
255
|
}
|
|
256
|
+
// Resolve auth once: API key needs no login round-trip.
|
|
257
|
+
async function resolveAccessToken() {
|
|
258
|
+
if (config.apiKey) {
|
|
259
|
+
console.log(`🔑 Using AGENTQ_API_KEY for authentication.`);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
const accessToken = await getAccessToken();
|
|
263
|
+
console.log(`🔑 Successfully obtained access token.`);
|
|
264
|
+
return accessToken;
|
|
265
|
+
}
|
|
228
266
|
// Main execution
|
|
229
267
|
(async () => {
|
|
230
268
|
if (tcId) {
|
|
231
269
|
console.log(`🔍 Fetching test case with tcId: ${tcId}`);
|
|
232
|
-
|
|
233
|
-
const accessToken = await getAccessToken();
|
|
234
|
-
console.log(`🔑 Successfully obtained access token.`);
|
|
270
|
+
const accessToken = await resolveAccessToken();
|
|
235
271
|
// Fetch the test case using the access token
|
|
236
272
|
const testCase = await fetchTestCase(tcId, accessToken);
|
|
237
273
|
console.log(`📄 Found test case: ${testCase.title}`);
|
|
@@ -241,9 +277,7 @@ ${expectationCode}
|
|
|
241
277
|
}
|
|
242
278
|
else if (testRunId) {
|
|
243
279
|
console.log(`🔍 Fetching test suite results for testRunId: ${testRunId}`);
|
|
244
|
-
|
|
245
|
-
const accessToken = await getAccessToken();
|
|
246
|
-
console.log(`🔑 Successfully obtained access token.`);
|
|
280
|
+
const accessToken = await resolveAccessToken();
|
|
247
281
|
// Fetch the test suite results
|
|
248
282
|
const testSuiteResponse = await fetchTestSuite(testRunId, accessToken);
|
|
249
283
|
const testResults = testSuiteResponse.results;
|
package/dist/testResult.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TestResult } from './types';
|
|
2
|
+
export declare function getAuthHeaders(): Promise<Record<string, string>>;
|
|
2
3
|
export declare function getAccessToken(): Promise<string>;
|
|
3
4
|
export declare function exportTestResult(tcId: string, testRunId: string, result: Partial<TestResult>): Promise<any>;
|
|
4
5
|
export declare function uploadArtifact(testRunId: string, testResultId: string, type: 'screenshot' | 'video', filePath: string): Promise<any>;
|
package/dist/testResult.js
CHANGED
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getAuthHeaders = getAuthHeaders;
|
|
6
7
|
exports.getAccessToken = getAccessToken;
|
|
7
8
|
exports.exportTestResult = exportTestResult;
|
|
8
9
|
exports.uploadArtifact = uploadArtifact;
|
|
@@ -16,17 +17,50 @@ dotenv.config({ quiet: true });
|
|
|
16
17
|
const config = {
|
|
17
18
|
apiBaseUrl: process.env.AGENTQ_API_URL || 'https://backend-app.agentq.id',
|
|
18
19
|
projectId: `${process.env.AGENTQ_PROJECT_ID}`,
|
|
20
|
+
apiKey: process.env.AGENTQ_API_KEY || null,
|
|
19
21
|
authEndpoint: '/auth/login',
|
|
20
22
|
authData: {
|
|
21
23
|
email: `${process.env.AGENTQ_EMAIL}`,
|
|
22
24
|
password: `${process.env.AGENTQ_PASSWORD}`
|
|
23
25
|
}
|
|
24
26
|
};
|
|
27
|
+
// Identify ourselves instead of the default axios/x.y UA, which bot
|
|
28
|
+
// protections (e.g. Cloudflare) commonly flag in CI environments.
|
|
29
|
+
let pkgVersion = '';
|
|
30
|
+
try {
|
|
31
|
+
pkgVersion = require('../package.json').version;
|
|
32
|
+
}
|
|
33
|
+
catch { /* best effort */ }
|
|
34
|
+
const USER_AGENT = `agentq-webdriverio${pkgVersion ? `/${pkgVersion}` : ''}`;
|
|
25
35
|
let cachedToken = null;
|
|
36
|
+
// API key auth (X-API-Key) when AGENTQ_API_KEY is set, otherwise the
|
|
37
|
+
// Bearer token obtained from email/password login.
|
|
38
|
+
async function getAuthHeaders() {
|
|
39
|
+
const headers = {
|
|
40
|
+
'accept': 'application/json',
|
|
41
|
+
'User-Agent': USER_AGENT
|
|
42
|
+
};
|
|
43
|
+
if (config.apiKey) {
|
|
44
|
+
headers['X-API-Key'] = config.apiKey;
|
|
45
|
+
// Optional attribution: with API key auth, AGENTQ_EMAIL names the company
|
|
46
|
+
// member shown as "Created By" on pushed results (no password needed).
|
|
47
|
+
if (process.env.AGENTQ_EMAIL) {
|
|
48
|
+
headers['X-Actor-Email'] = process.env.AGENTQ_EMAIL;
|
|
49
|
+
}
|
|
50
|
+
return headers;
|
|
51
|
+
}
|
|
52
|
+
headers['Authorization'] = `Bearer ${await getAccessToken()}`;
|
|
53
|
+
return headers;
|
|
54
|
+
}
|
|
26
55
|
async function getAccessToken() {
|
|
27
56
|
if (cachedToken) {
|
|
28
57
|
return cachedToken;
|
|
29
58
|
}
|
|
59
|
+
if (!process.env.AGENTQ_EMAIL || !process.env.AGENTQ_PASSWORD) {
|
|
60
|
+
console.error('Error: No credentials provided.');
|
|
61
|
+
console.error('Set AGENTQ_API_KEY (recommended), or AGENTQ_EMAIL and AGENTQ_PASSWORD.');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
30
64
|
try {
|
|
31
65
|
// console.log(`Authenticating with: ${config.apiBaseUrl}${config.authEndpoint}`);
|
|
32
66
|
const response = await axios_1.default.post(`${config.apiBaseUrl}${config.authEndpoint}`, config.authData, {
|
|
@@ -54,14 +88,13 @@ async function getAccessToken() {
|
|
|
54
88
|
}
|
|
55
89
|
}
|
|
56
90
|
async function exportTestResult(tcId, testRunId, result) {
|
|
57
|
-
const
|
|
91
|
+
const authHeaders = await getAuthHeaders();
|
|
58
92
|
const apiUrl = `${config.apiBaseUrl}/projects/${config.projectId}/test-runs/${testRunId}/test-results/tcId/${tcId}`;
|
|
59
93
|
try {
|
|
60
94
|
// console.log(`Pushing test result to: ${apiUrl}`);
|
|
61
95
|
const response = await axios_1.default.patch(apiUrl, result, {
|
|
62
96
|
headers: {
|
|
63
|
-
|
|
64
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
97
|
+
...authHeaders,
|
|
65
98
|
'Content-Type': 'application/json'
|
|
66
99
|
}
|
|
67
100
|
});
|
|
@@ -84,7 +117,7 @@ async function exportTestResult(tcId, testRunId, result) {
|
|
|
84
117
|
}
|
|
85
118
|
}
|
|
86
119
|
async function uploadArtifact(testRunId, testResultId, type, filePath) {
|
|
87
|
-
const
|
|
120
|
+
const authHeaders = await getAuthHeaders();
|
|
88
121
|
const apiUrl = `${config.apiBaseUrl}/projects/${config.projectId}/test-runs/${testRunId}/test-results/${testResultId}/${type}`;
|
|
89
122
|
if (!fs_1.default.existsSync(filePath)) {
|
|
90
123
|
console.warn(`⚠️ Artifact file not found: ${filePath}`);
|
|
@@ -100,7 +133,7 @@ async function uploadArtifact(testRunId, testResultId, type, filePath) {
|
|
|
100
133
|
// console.log(`Uploading ${type} to: ${apiUrl}`);
|
|
101
134
|
const response = await axios_1.default.post(apiUrl, formData, {
|
|
102
135
|
headers: {
|
|
103
|
-
|
|
136
|
+
...authHeaders,
|
|
104
137
|
'Content-Type': 'multipart/form-data'
|
|
105
138
|
}
|
|
106
139
|
});
|
package/package.json
CHANGED
package/agentq.config.json
DELETED