testdriverai 7.1.1 → 7.1.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/agent/lib/commands.js +7 -2
- package/docs/docs.json +23 -30
- package/docs/v7/getting-started/quickstart.mdx +11 -9
- package/docs/v7/getting-started/running-and-debugging.mdx +3 -2
- package/docs/v7/getting-started/writing-tests.mdx +4 -5
- package/docs/v7/presets/chrome.mdx +38 -41
- package/docs/v7/presets/electron.mdx +107 -100
- package/docs/v7/presets/webapp.mdx +40 -43
- package/interfaces/cli/commands/init.js +4 -4
- package/interfaces/vitest-plugin.mjs +36 -9
- package/lib/vitest/hooks.mjs +5 -1
- package/package.json +1 -1
- package/sdk.js +59 -6
- package/test/testdriver/exec-output.test.mjs +1 -1
- package/test/testdriver/exec-pwsh.test.mjs +1 -1
- package/test/testdriver/focus-window.test.mjs +1 -1
- package/test/testdriver/hover-image.test.mjs +1 -1
- package/test/testdriver/match-image.test.mjs +1 -1
- package/test/testdriver/scroll-keyboard.test.mjs +1 -1
- package/test/testdriver/scroll-until-image.test.mjs +1 -1
- package/test/testdriver/scroll-until-text.test.mjs +18 -1
- package/test/testdriver/scroll.test.mjs +1 -1
- package/test/testdriver/setup/lifecycleHelpers.mjs +105 -5
- package/test/testdriver/setup/testHelpers.mjs +6 -2
- package/vitest.config.mjs +2 -2
- package/test/testdriver/exec-js.test.mjs +0 -43
|
@@ -423,7 +423,7 @@ export default function testDriverPlugin(options = {}) {
|
|
|
423
423
|
options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
|
|
424
424
|
pluginState.ciProvider = detectCI();
|
|
425
425
|
pluginState.gitInfo = getGitInfo();
|
|
426
|
-
|
|
426
|
+
|
|
427
427
|
// Store TestDriver-specific options (excluding plugin-specific ones)
|
|
428
428
|
const { apiKey, apiRoot, ...testDriverOptions } = options;
|
|
429
429
|
pluginState.testDriverOptions = testDriverOptions;
|
|
@@ -441,7 +441,13 @@ export default function testDriverPlugin(options = {}) {
|
|
|
441
441
|
logger.debug("Global TestDriver options:", testDriverOptions);
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
|
|
444
|
+
// Create reporter instance
|
|
445
|
+
const reporter = new TestDriverReporter(options);
|
|
446
|
+
|
|
447
|
+
// Add name property for Vitest
|
|
448
|
+
reporter.name = 'testdriver';
|
|
449
|
+
|
|
450
|
+
return reporter;
|
|
445
451
|
}
|
|
446
452
|
|
|
447
453
|
/**
|
|
@@ -458,6 +464,10 @@ class TestDriverReporter {
|
|
|
458
464
|
this.ctx = ctx;
|
|
459
465
|
logger.debug("onInit called - UPDATED VERSION");
|
|
460
466
|
|
|
467
|
+
// Store project root for making file paths relative
|
|
468
|
+
pluginState.projectRoot = ctx.config.root || process.cwd();
|
|
469
|
+
logger.debug("Project root:", pluginState.projectRoot);
|
|
470
|
+
|
|
461
471
|
// NOW read the API key and API root (after setupFiles have run, including dotenv/config)
|
|
462
472
|
pluginState.apiKey = this.options.apiKey || process.env.TD_API_KEY;
|
|
463
473
|
pluginState.apiRoot = this.options.apiRoot || process.env.TD_API_ROOT || "https://testdriver-api.onrender.com";
|
|
@@ -668,11 +678,15 @@ class TestDriverReporter {
|
|
|
668
678
|
dashcamUrl = testResult.dashcamUrl || null;
|
|
669
679
|
const platform = testResult.platform || null;
|
|
670
680
|
sessionId = testResult.sessionId || null;
|
|
671
|
-
|
|
681
|
+
const absolutePath =
|
|
672
682
|
testResult.testFile ||
|
|
673
683
|
test.file?.filepath ||
|
|
674
684
|
test.file?.name ||
|
|
675
685
|
"unknown";
|
|
686
|
+
// Make path relative to project root
|
|
687
|
+
testFile = pluginState.projectRoot && absolutePath !== "unknown"
|
|
688
|
+
? path.relative(pluginState.projectRoot, absolutePath)
|
|
689
|
+
: absolutePath;
|
|
676
690
|
testOrder =
|
|
677
691
|
testResult.testOrder !== undefined ? testResult.testOrder : 0;
|
|
678
692
|
// Don't override duration from file - use Vitest's result.duration
|
|
@@ -696,7 +710,7 @@ class TestDriverReporter {
|
|
|
696
710
|
logger.debug(`No result file found for test: ${test.id}`);
|
|
697
711
|
// Fallback to test object properties - try multiple sources
|
|
698
712
|
// In Vitest, the file path is on test.module.task.filepath
|
|
699
|
-
|
|
713
|
+
const absolutePath =
|
|
700
714
|
test.module?.task?.filepath ||
|
|
701
715
|
test.module?.file?.filepath ||
|
|
702
716
|
test.module?.file?.name ||
|
|
@@ -706,13 +720,17 @@ class TestDriverReporter {
|
|
|
706
720
|
test.suite?.file?.name ||
|
|
707
721
|
test.location?.file ||
|
|
708
722
|
"unknown";
|
|
723
|
+
// Make path relative to project root
|
|
724
|
+
testFile = pluginState.projectRoot && absolutePath !== "unknown"
|
|
725
|
+
? path.relative(pluginState.projectRoot, absolutePath)
|
|
726
|
+
: absolutePath;
|
|
709
727
|
logger.debug(`Resolved testFile: ${testFile}`);
|
|
710
728
|
}
|
|
711
729
|
} catch (error) {
|
|
712
730
|
logger.error("Failed to read test result file:", error.message);
|
|
713
731
|
// Fallback to test object properties - try multiple sources
|
|
714
732
|
// In Vitest, the file path is on test.module.task.filepath
|
|
715
|
-
|
|
733
|
+
const absolutePath =
|
|
716
734
|
test.module?.task?.filepath ||
|
|
717
735
|
test.module?.file?.filepath ||
|
|
718
736
|
test.module?.file?.name ||
|
|
@@ -722,6 +740,10 @@ class TestDriverReporter {
|
|
|
722
740
|
test.suite?.file?.name ||
|
|
723
741
|
test.location?.file ||
|
|
724
742
|
"unknown";
|
|
743
|
+
// Make path relative to project root
|
|
744
|
+
testFile = pluginState.projectRoot && absolutePath !== "unknown"
|
|
745
|
+
? path.relative(pluginState.projectRoot, absolutePath)
|
|
746
|
+
: absolutePath;
|
|
725
747
|
logger.debug(`Resolved testFile from fallback: ${testFile}`);
|
|
726
748
|
}
|
|
727
749
|
|
|
@@ -901,8 +923,9 @@ function getGitInfo() {
|
|
|
901
923
|
encoding: "utf8",
|
|
902
924
|
stdio: ["pipe", "pipe", "ignore"]
|
|
903
925
|
}).trim();
|
|
926
|
+
logger.debug("Git commit from local:", info.commit);
|
|
904
927
|
} catch (e) {
|
|
905
|
-
|
|
928
|
+
logger.debug("Failed to get git commit:", e.message);
|
|
906
929
|
}
|
|
907
930
|
}
|
|
908
931
|
|
|
@@ -912,8 +935,9 @@ function getGitInfo() {
|
|
|
912
935
|
encoding: "utf8",
|
|
913
936
|
stdio: ["pipe", "pipe", "ignore"]
|
|
914
937
|
}).trim();
|
|
938
|
+
logger.debug("Git branch from local:", info.branch);
|
|
915
939
|
} catch (e) {
|
|
916
|
-
|
|
940
|
+
logger.debug("Failed to get git branch:", e.message);
|
|
917
941
|
}
|
|
918
942
|
}
|
|
919
943
|
|
|
@@ -923,8 +947,9 @@ function getGitInfo() {
|
|
|
923
947
|
encoding: "utf8",
|
|
924
948
|
stdio: ["pipe", "pipe", "ignore"]
|
|
925
949
|
}).trim();
|
|
950
|
+
logger.debug("Git author from local:", info.author);
|
|
926
951
|
} catch (e) {
|
|
927
|
-
|
|
952
|
+
logger.debug("Failed to get git author:", e.message);
|
|
928
953
|
}
|
|
929
954
|
}
|
|
930
955
|
|
|
@@ -941,12 +966,14 @@ function getGitInfo() {
|
|
|
941
966
|
const match = remoteUrl.match(/[:/]([^/:]+\/[^/:]+?)(\.git)?$/);
|
|
942
967
|
if (match) {
|
|
943
968
|
info.repo = match[1];
|
|
969
|
+
logger.debug("Git repo from local:", info.repo);
|
|
944
970
|
}
|
|
945
971
|
} catch (e) {
|
|
946
|
-
|
|
972
|
+
logger.debug("Failed to get git repo:", e.message);
|
|
947
973
|
}
|
|
948
974
|
}
|
|
949
975
|
|
|
976
|
+
logger.info("Collected git info:", info);
|
|
950
977
|
return info;
|
|
951
978
|
}
|
|
952
979
|
|
package/lib/vitest/hooks.mjs
CHANGED
|
@@ -246,7 +246,11 @@ export function TestDriver(context, options = {}) {
|
|
|
246
246
|
if (dashcamUrl) {
|
|
247
247
|
const testId = context.task.id;
|
|
248
248
|
const platform = testdriver.os || 'linux';
|
|
249
|
-
const
|
|
249
|
+
const absolutePath = context.task.file?.filepath || context.task.file?.name || 'unknown';
|
|
250
|
+
const projectRoot = process.cwd();
|
|
251
|
+
const testFile = absolutePath !== 'unknown'
|
|
252
|
+
? path.relative(projectRoot, absolutePath)
|
|
253
|
+
: absolutePath;
|
|
250
254
|
|
|
251
255
|
// Create results directory if it doesn't exist
|
|
252
256
|
const resultsDir = path.join(os.tmpdir(), 'testdriver-results');
|
package/package.json
CHANGED
package/sdk.js
CHANGED
|
@@ -339,12 +339,12 @@ class Element {
|
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
// Determine threshold:
|
|
342
|
-
// - If cacheKey is provided, enable cache (threshold = 0.
|
|
342
|
+
// - If cacheKey is provided, enable cache (threshold = 0.01 or custom)
|
|
343
343
|
// - If no cacheKey, disable cache (threshold = -1) unless explicitly overridden
|
|
344
344
|
let threshold;
|
|
345
345
|
if (cacheKey) {
|
|
346
346
|
// cacheKey provided - enable cache with threshold
|
|
347
|
-
threshold = cacheThreshold ?? 0.
|
|
347
|
+
threshold = cacheThreshold ?? 0.01;
|
|
348
348
|
} else if (cacheThreshold !== null) {
|
|
349
349
|
// Explicit threshold provided without cacheKey
|
|
350
350
|
threshold = cacheThreshold;
|
|
@@ -1255,11 +1255,65 @@ class TestDriverSDK {
|
|
|
1255
1255
|
console.log('[provision.chrome] ✅ Dashcam started');
|
|
1256
1256
|
}
|
|
1257
1257
|
|
|
1258
|
+
// Set up Chrome profile with preferences
|
|
1259
|
+
const shell = this.os === 'windows' ? 'pwsh' : 'sh';
|
|
1260
|
+
const userDataDir = this.os === 'windows'
|
|
1261
|
+
? 'C:\\Users\\testdriver\\AppData\\Local\\TestDriver\\Chrome'
|
|
1262
|
+
: '/tmp/testdriver-chrome-profile';
|
|
1263
|
+
|
|
1264
|
+
// Create user data directory and Default profile directory
|
|
1265
|
+
const defaultProfileDir = this.os === 'windows'
|
|
1266
|
+
? `${userDataDir}\\Default`
|
|
1267
|
+
: `${userDataDir}/Default`;
|
|
1268
|
+
|
|
1269
|
+
const createDirCmd = this.os === 'windows'
|
|
1270
|
+
? `New-Item -ItemType Directory -Path "${defaultProfileDir}" -Force | Out-Null`
|
|
1271
|
+
: `mkdir -p "${defaultProfileDir}"`;
|
|
1272
|
+
|
|
1273
|
+
await this.exec(shell, createDirCmd, 10000, true);
|
|
1274
|
+
|
|
1275
|
+
// Write Chrome preferences
|
|
1276
|
+
const chromePrefs = {
|
|
1277
|
+
credentials_enable_service: false,
|
|
1278
|
+
profile: {
|
|
1279
|
+
password_manager_enabled: false,
|
|
1280
|
+
default_content_setting_values: {}
|
|
1281
|
+
},
|
|
1282
|
+
signin: {
|
|
1283
|
+
allowed: false
|
|
1284
|
+
},
|
|
1285
|
+
sync: {
|
|
1286
|
+
requested: false,
|
|
1287
|
+
first_setup_complete: true,
|
|
1288
|
+
sync_all_os_types: false
|
|
1289
|
+
},
|
|
1290
|
+
autofill: {
|
|
1291
|
+
enabled: false
|
|
1292
|
+
},
|
|
1293
|
+
local_state: {
|
|
1294
|
+
browser: {
|
|
1295
|
+
has_seen_welcome_page: true
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
const prefsPath = this.os === 'windows'
|
|
1301
|
+
? `${defaultProfileDir}\\Preferences`
|
|
1302
|
+
: `${defaultProfileDir}/Preferences`;
|
|
1303
|
+
|
|
1304
|
+
const prefsJson = JSON.stringify(chromePrefs, null, 2);
|
|
1305
|
+
const writePrefCmd = this.os === 'windows'
|
|
1306
|
+
? `Set-Content -Path "${prefsPath}" -Value '${prefsJson.replace(/'/g, "''")}'`
|
|
1307
|
+
: `cat > "${prefsPath}" << 'EOF'\n${prefsJson}\nEOF`;
|
|
1308
|
+
|
|
1309
|
+
await this.exec(shell, writePrefCmd, 10000, true);
|
|
1310
|
+
console.log('[provision.chrome] ✅ Chrome preferences configured');
|
|
1311
|
+
|
|
1258
1312
|
// Build Chrome launch command
|
|
1259
1313
|
const chromeArgs = [];
|
|
1260
1314
|
if (maximized) chromeArgs.push('--start-maximized');
|
|
1261
1315
|
if (guest) chromeArgs.push('--guest');
|
|
1262
|
-
chromeArgs.push('--disable-fre', '--no-default-browser-check', '--no-first-run');
|
|
1316
|
+
chromeArgs.push('--disable-fre', '--no-default-browser-check', '--no-first-run', '--disable-infobars', `--user-data-dir=${userDataDir}`);
|
|
1263
1317
|
|
|
1264
1318
|
// Add dashcam-chrome extension on Linux
|
|
1265
1319
|
if (this.os === 'linux') {
|
|
@@ -1267,7 +1321,6 @@ class TestDriverSDK {
|
|
|
1267
1321
|
}
|
|
1268
1322
|
|
|
1269
1323
|
// Launch Chrome
|
|
1270
|
-
const shell = this.os === 'windows' ? 'pwsh' : 'sh';
|
|
1271
1324
|
|
|
1272
1325
|
if (this.os === 'windows') {
|
|
1273
1326
|
const argsString = chromeArgs.map(arg => `"${arg}"`).join(', ');
|
|
@@ -1665,12 +1718,12 @@ class TestDriverSDK {
|
|
|
1665
1718
|
}
|
|
1666
1719
|
|
|
1667
1720
|
// Determine threshold:
|
|
1668
|
-
// - If cacheKey is provided, enable cache (threshold = 0.
|
|
1721
|
+
// - If cacheKey is provided, enable cache (threshold = 0.01 or custom)
|
|
1669
1722
|
// - If no cacheKey, disable cache (threshold = -1) unless explicitly overridden
|
|
1670
1723
|
let threshold;
|
|
1671
1724
|
if (cacheKey) {
|
|
1672
1725
|
// cacheKey provided - enable cache with threshold
|
|
1673
|
-
threshold = cacheThreshold ?? 0.
|
|
1726
|
+
threshold = cacheThreshold ?? 0.01;
|
|
1674
1727
|
} else if (cacheThreshold !== null) {
|
|
1675
1728
|
// Explicit threshold provided without cacheKey
|
|
1676
1729
|
threshold = cacheThreshold;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
8
8
|
|
|
9
|
-
describe.
|
|
9
|
+
describe.skip("Exec Output Test", () => {
|
|
10
10
|
it(
|
|
11
11
|
"should set date using PowerShell and navigate to calendar",
|
|
12
12
|
async (context) => {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
7
7
|
import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
8
8
|
|
|
9
|
-
describe.
|
|
9
|
+
describe.skip("Exec PowerShell Test", () => {
|
|
10
10
|
it(
|
|
11
11
|
"should generate random email using PowerShell and enter it",
|
|
12
12
|
async (context) => {
|
|
@@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest";
|
|
|
7
7
|
import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
8
8
|
|
|
9
9
|
describe("Focus Window Test", () => {
|
|
10
|
-
it.
|
|
10
|
+
it.skip(
|
|
11
11
|
"should click Microsoft Edge icon and focus Google Chrome",
|
|
12
12
|
async (context) => {
|
|
13
13
|
const testdriver = TestDriver(context, { headless: true });
|
|
@@ -9,7 +9,7 @@ import { performLogin } from "./setup/testHelpers.mjs";
|
|
|
9
9
|
|
|
10
10
|
describe("Hover Image Test", () => {
|
|
11
11
|
it("should click on shopping cart icon and verify empty cart", async (context) => {
|
|
12
|
-
const testdriver = TestDriver(context, { headless:
|
|
12
|
+
const testdriver = TestDriver(context, { headless: false });
|
|
13
13
|
|
|
14
14
|
// provision.chrome() automatically calls ready() and starts dashcam
|
|
15
15
|
await testdriver.provision.chrome({
|
|
@@ -14,7 +14,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
14
14
|
const __dirname = dirname(__filename);
|
|
15
15
|
|
|
16
16
|
describe("Match Image Test", () => {
|
|
17
|
-
it("should match shopping cart image and verify empty cart", async (context) => {
|
|
17
|
+
it.skip("should match shopping cart image and verify empty cart", async (context) => {
|
|
18
18
|
const testdriver = TestDriver(context, { headless: true });
|
|
19
19
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
20
20
|
|
|
@@ -27,7 +27,7 @@ describe("Scroll Keyboard Test", () => {
|
|
|
27
27
|
"The Hamster Dance, large heading at top of page",
|
|
28
28
|
);
|
|
29
29
|
await heading.click();
|
|
30
|
-
await testdriver.scroll("down", 1000);
|
|
30
|
+
await testdriver.scroll("down", { amount: 1000 });
|
|
31
31
|
|
|
32
32
|
// Assert the page is scrolled down
|
|
33
33
|
const result = await testdriver.assert(
|
|
@@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest";
|
|
|
7
7
|
import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
8
8
|
|
|
9
9
|
describe("Scroll Until Image Test", () => {
|
|
10
|
-
it("should scroll until brown colored house image appears", async (context) => {
|
|
10
|
+
it.skip("should scroll until brown colored house image appears", async (context) => {
|
|
11
11
|
const testdriver = TestDriver(context, { headless: true });
|
|
12
12
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
13
13
|
|
|
@@ -18,7 +18,24 @@ describe("Scroll Until Text Test", () => {
|
|
|
18
18
|
|
|
19
19
|
// Scroll until text appears
|
|
20
20
|
await testdriver.focusApplication("Google Chrome");
|
|
21
|
-
|
|
21
|
+
// Scroll until text appears
|
|
22
|
+
let found = false;
|
|
23
|
+
let scrollCount = 0;
|
|
24
|
+
const maxScrolls = 10;
|
|
25
|
+
|
|
26
|
+
while (!found && scrollCount < maxScrolls) {
|
|
27
|
+
const findResult = await testdriver.find("testdriver socks");
|
|
28
|
+
if (findResult) {
|
|
29
|
+
found = true;
|
|
30
|
+
} else {
|
|
31
|
+
await testdriver.scroll({ direction: "down" });
|
|
32
|
+
scrollCount++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!found) {
|
|
37
|
+
throw new Error(`Failed to find "testdriver socks" after ${maxScrolls} scrolls`);
|
|
38
|
+
}
|
|
22
39
|
|
|
23
40
|
// Assert testdriver socks appears on screen
|
|
24
41
|
await testdriver.focusApplication("Google Chrome");
|
|
@@ -32,7 +32,7 @@ describe("Scroll Test", () => {
|
|
|
32
32
|
await heading.click();
|
|
33
33
|
|
|
34
34
|
// Scroll down
|
|
35
|
-
await testdriver.scroll("down", 1000);
|
|
35
|
+
await testdriver.scroll("down", { amount: 1000 });
|
|
36
36
|
|
|
37
37
|
// Assert page is scrolled
|
|
38
38
|
const result = await testdriver.assert("the page is scrolled down, the hamster dance heading is not visible on the page");
|
|
@@ -123,7 +123,7 @@ export async function launchChrome(
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
|
-
* Launch Chrome for Testing browser with
|
|
126
|
+
* Launch Chrome for Testing browser with custom profile
|
|
127
127
|
* @param {TestDriver} client - TestDriver client
|
|
128
128
|
* @param {string} url - URL to open (default: https://testdriver-sandbox.vercel.app/)
|
|
129
129
|
*/
|
|
@@ -132,19 +132,69 @@ export async function launchChromeForTesting(
|
|
|
132
132
|
url = "http://testdriver-sandbox.vercel.app/",
|
|
133
133
|
) {
|
|
134
134
|
const shell = client.os === "windows" ? "pwsh" : "sh";
|
|
135
|
+
const userDataDir = client.os === "windows"
|
|
136
|
+
? "C:\\Users\\testdriver\\AppData\\Local\\TestDriver\\Chrome"
|
|
137
|
+
: "/tmp/testdriver-chrome-profile";
|
|
138
|
+
|
|
139
|
+
// Create user data directory and Default profile directory
|
|
140
|
+
const defaultProfileDir = client.os === "windows"
|
|
141
|
+
? `${userDataDir}\\Default`
|
|
142
|
+
: `${userDataDir}/Default`;
|
|
143
|
+
|
|
144
|
+
const createDirCmd = client.os === "windows"
|
|
145
|
+
? `New-Item -ItemType Directory -Path "${defaultProfileDir}" -Force | Out-Null`
|
|
146
|
+
: `mkdir -p "${defaultProfileDir}"`;
|
|
147
|
+
|
|
148
|
+
await client.exec(shell, createDirCmd, 10000, true);
|
|
149
|
+
|
|
150
|
+
// Write Chrome preferences
|
|
151
|
+
const chromePrefs = {
|
|
152
|
+
credentials_enable_service: false,
|
|
153
|
+
profile: {
|
|
154
|
+
password_manager_enabled: false,
|
|
155
|
+
default_content_setting_values: {}
|
|
156
|
+
},
|
|
157
|
+
signin: {
|
|
158
|
+
allowed: false
|
|
159
|
+
},
|
|
160
|
+
sync: {
|
|
161
|
+
requested: false,
|
|
162
|
+
first_setup_complete: true,
|
|
163
|
+
sync_all_os_types: false
|
|
164
|
+
},
|
|
165
|
+
autofill: {
|
|
166
|
+
enabled: false
|
|
167
|
+
},
|
|
168
|
+
local_state: {
|
|
169
|
+
browser: {
|
|
170
|
+
has_seen_welcome_page: true
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const prefsPath = client.os === "windows"
|
|
176
|
+
? `${defaultProfileDir}\\Preferences`
|
|
177
|
+
: `${defaultProfileDir}/Preferences`;
|
|
178
|
+
|
|
179
|
+
const prefsJson = JSON.stringify(chromePrefs, null, 2);
|
|
180
|
+
const writePrefCmd = client.os === "windows"
|
|
181
|
+
? `Set-Content -Path "${prefsPath}" -Value '${prefsJson.replace(/'/g, "''")}'`
|
|
182
|
+
: `cat > "${prefsPath}" << 'EOF'\n${prefsJson}\nEOF`;
|
|
183
|
+
|
|
184
|
+
await client.exec(shell, writePrefCmd, 10000, true);
|
|
135
185
|
|
|
136
186
|
if (client.os === "windows") {
|
|
137
187
|
// Windows Chrome for Testing path would need to be determined
|
|
138
188
|
// For now, fallback to regular Chrome on Windows
|
|
139
189
|
await client.exec(
|
|
140
190
|
"pwsh",
|
|
141
|
-
`Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--
|
|
191
|
+
`Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--user-data-dir=${userDataDir}", "--disable-fre", "--no-default-browser-check", "--no-first-run", "${url}"`,
|
|
142
192
|
30000,
|
|
143
193
|
);
|
|
144
194
|
} else {
|
|
145
195
|
await client.exec(
|
|
146
196
|
shell,
|
|
147
|
-
`chrome-for-testing --start-maximized --disable-fre --no-default-browser-check --no-first-run --
|
|
197
|
+
`chrome-for-testing --start-maximized --disable-fre --no-default-browser-check --no-first-run --user-data-dir=${userDataDir} "${url}" >/dev/null 2>&1 &`,
|
|
148
198
|
30000,
|
|
149
199
|
);
|
|
150
200
|
}
|
|
@@ -168,19 +218,69 @@ export async function launchChromeExtension(
|
|
|
168
218
|
url = "http://testdriver-sandbox.vercel.app/",
|
|
169
219
|
) {
|
|
170
220
|
const shell = client.os === "windows" ? "pwsh" : "sh";
|
|
221
|
+
const userDataDir = client.os === "windows"
|
|
222
|
+
? "C:\\Users\\testdriver\\AppData\\Local\\TestDriver\\Chrome"
|
|
223
|
+
: "/tmp/testdriver-chrome-profile";
|
|
224
|
+
|
|
225
|
+
// Create user data directory and Default profile directory
|
|
226
|
+
const defaultProfileDir = client.os === "windows"
|
|
227
|
+
? `${userDataDir}\\Default`
|
|
228
|
+
: `${userDataDir}/Default`;
|
|
229
|
+
|
|
230
|
+
const createDirCmd = client.os === "windows"
|
|
231
|
+
? `New-Item -ItemType Directory -Path "${defaultProfileDir}" -Force | Out-Null`
|
|
232
|
+
: `mkdir -p "${defaultProfileDir}"`;
|
|
233
|
+
|
|
234
|
+
await client.exec(shell, createDirCmd, 10000, true);
|
|
235
|
+
|
|
236
|
+
// Write Chrome preferences
|
|
237
|
+
const chromePrefs = {
|
|
238
|
+
credentials_enable_service: false,
|
|
239
|
+
profile: {
|
|
240
|
+
password_manager_enabled: false,
|
|
241
|
+
default_content_setting_values: {}
|
|
242
|
+
},
|
|
243
|
+
signin: {
|
|
244
|
+
allowed: false
|
|
245
|
+
},
|
|
246
|
+
sync: {
|
|
247
|
+
requested: false,
|
|
248
|
+
first_setup_complete: true,
|
|
249
|
+
sync_all_os_types: false
|
|
250
|
+
},
|
|
251
|
+
autofill: {
|
|
252
|
+
enabled: false
|
|
253
|
+
},
|
|
254
|
+
local_state: {
|
|
255
|
+
browser: {
|
|
256
|
+
has_seen_welcome_page: true
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const prefsPath = client.os === "windows"
|
|
262
|
+
? `${defaultProfileDir}\\Preferences`
|
|
263
|
+
: `${defaultProfileDir}/Preferences`;
|
|
264
|
+
|
|
265
|
+
const prefsJson = JSON.stringify(chromePrefs, null, 2);
|
|
266
|
+
const writePrefCmd = client.os === "windows"
|
|
267
|
+
? `Set-Content -Path "${prefsPath}" -Value '${prefsJson.replace(/'/g, "''")}'`
|
|
268
|
+
: `cat > "${prefsPath}" << 'EOF'\n${prefsJson}\nEOF`;
|
|
269
|
+
|
|
270
|
+
await client.exec(shell, writePrefCmd, 10000, true);
|
|
171
271
|
|
|
172
272
|
if (client.os === "windows") {
|
|
173
273
|
// Windows Chrome for Testing path would need to be determined
|
|
174
274
|
// For now, fallback to regular Chrome on Windows
|
|
175
275
|
await client.exec(
|
|
176
276
|
"pwsh",
|
|
177
|
-
`Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--load-extension=${extensionId}", "${url}"`,
|
|
277
|
+
`Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--user-data-dir=${userDataDir}", "--load-extension=${extensionId}", "${url}"`,
|
|
178
278
|
30000,
|
|
179
279
|
);
|
|
180
280
|
} else {
|
|
181
281
|
await client.exec(
|
|
182
282
|
shell,
|
|
183
|
-
`chrome-for-testing --start-maximized --disable-fre --no-default-browser-check --no-first-run --load-extension=${extensionId} "${url}" >/dev/null 2>&1 &`,
|
|
283
|
+
`chrome-for-testing --start-maximized --disable-fre --no-default-browser-check --no-first-run --user-data-dir=${userDataDir} --load-extension=${extensionId} "${url}" >/dev/null 2>&1 &`,
|
|
184
284
|
30000,
|
|
185
285
|
);
|
|
186
286
|
}
|
|
@@ -511,9 +511,13 @@ export async function teardownTest(client, options = {}) {
|
|
|
511
511
|
fs.mkdirSync(dir, { recursive: true });
|
|
512
512
|
}
|
|
513
513
|
|
|
514
|
-
// Get test file path
|
|
515
|
-
const
|
|
514
|
+
// Get test file path - make it relative to project root
|
|
515
|
+
const absolutePath =
|
|
516
516
|
options.task.file?.filepath || options.task.file?.name || "unknown";
|
|
517
|
+
const projectRoot = process.cwd();
|
|
518
|
+
const testFile = absolutePath !== "unknown"
|
|
519
|
+
? path.relative(projectRoot, absolutePath)
|
|
520
|
+
: absolutePath;
|
|
517
521
|
|
|
518
522
|
// Calculate test order (index within parent suite)
|
|
519
523
|
let testOrder = 0;
|
package/vitest.config.mjs
CHANGED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TestDriver SDK - Exec JS Test (Vitest)
|
|
3
|
-
* Converted from: testdriver/acceptance/exec-js.yaml
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, expect, it } from "vitest";
|
|
7
|
-
import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
8
|
-
|
|
9
|
-
describe("Exec JavaScript Test", () => {
|
|
10
|
-
it("should fetch user data from API and enter email", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, { headless: true });
|
|
12
|
-
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
// Execute JavaScript to fetch user data
|
|
16
|
-
const userEmail = await testdriver.exec(
|
|
17
|
-
"js",
|
|
18
|
-
`
|
|
19
|
-
const response = await fetch('https://jsonplaceholder.typicode.com/users');
|
|
20
|
-
const user = await response.json();
|
|
21
|
-
console.log('user', user[0]);
|
|
22
|
-
result = user[0].email;
|
|
23
|
-
`,
|
|
24
|
-
10000,
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
expect(userEmail).toBeTruthy();
|
|
28
|
-
expect(userEmail).toContain("@");
|
|
29
|
-
|
|
30
|
-
// Enter email in username field
|
|
31
|
-
const usernameField = await testdriver.find(
|
|
32
|
-
"Username, input field for username",
|
|
33
|
-
);
|
|
34
|
-
await usernameField.click();
|
|
35
|
-
await testdriver.type(userEmail);
|
|
36
|
-
|
|
37
|
-
// Assert email is in the field
|
|
38
|
-
const result = await testdriver.assert(
|
|
39
|
-
'the username field contains "Sincere@april.biz" which is a valid email address',
|
|
40
|
-
);
|
|
41
|
-
expect(result).toBeTruthy();
|
|
42
|
-
});
|
|
43
|
-
});
|