@applitools/eyes-browser 1.5.17 → 1.5.18

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/index.js +510 -39
  3. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.18](https://github.com/Applitools-Dev/sdk/compare/js/eyes-browser@1.5.17...js/eyes-browser@1.5.18) (2025-12-14)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * @applitools/nml-client bumped to 1.11.13
9
+
10
+ * @applitools/core-base bumped to 1.31.0
11
+ #### Features
12
+
13
+ * Baseline branch fallback list | FLD-3837 ([#3373](https://github.com/Applitools-Dev/sdk/issues/3373)) ([e94bb10](https://github.com/Applitools-Dev/sdk/commit/e94bb10ad6b49322a56e4ce6dfde560b237e9ac0))
14
+ * @applitools/core bumped to 4.54.0
15
+ #### Features
16
+
17
+ * Baseline branch fallback list | FLD-3837 ([#3373](https://github.com/Applitools-Dev/sdk/issues/3373)) ([e94bb10](https://github.com/Applitools-Dev/sdk/commit/e94bb10ad6b49322a56e4ce6dfde560b237e9ac0))
18
+
19
+
20
+
21
+ * @applitools/ec-client bumped to 1.12.15
22
+
23
+ * @applitools/eyes bumped to 1.36.18
24
+
25
+
3
26
  ## [1.5.17](https://github.com/Applitools-Dev/sdk/compare/js/eyes-browser@1.5.16...js/eyes-browser@1.5.17) (2025-12-07)
4
27
 
5
28
 
package/dist/index.js CHANGED
@@ -22142,7 +22142,7 @@ var require_requests = __commonJS({
22142
22142
  logger.log('Request "openEyes" called with settings', settings);
22143
22143
  const account = await getAccountInfoWithCache({ settings });
22144
22144
  if (account.processKeepaliveIntervalSec) {
22145
- heartbeat.acquire({
22145
+ settings.processId = heartbeat.acquire({
22146
22146
  eyesServerUrl: settings.eyesServerUrl,
22147
22147
  apiKey: settings.apiKey,
22148
22148
  proxy: settings.proxy,
@@ -22482,6 +22482,9 @@ var require_requests = __commonJS({
22482
22482
  supportedEnvironmentsUrl,
22483
22483
  uploadUrl,
22484
22484
  serverConcurrency: (0, server_concurrency_converter_1.handleConcurrency)(serverConcurrency),
22485
+ scmSettings: {
22486
+ isFallbackBranchListEnabled: res.isFallbackBranchListEnabled
22487
+ },
22485
22488
  ...rest
22486
22489
  };
22487
22490
  logger.mask(result.ufgServer.accessToken);
@@ -23107,6 +23110,8 @@ var require_requests = __commonJS({
23107
23110
  baselineBranchName: settings.baselineBranchName,
23108
23111
  compareWithParentBranch: settings.compareWithParentBranch,
23109
23112
  parentBranchBaselineSavedBefore: settings.gitBranchingTimestamp,
23113
+ branchLookupFallbackList: settings.branchLookupFallbackList,
23114
+ gitBranchName: settings.gitBranchName,
23110
23115
  ignoreBaseline: settings.ignoreBaseline,
23111
23116
  saveDiffs: settings.saveDiffs,
23112
23117
  timeout: settings.abortIdleTestTimeout,
@@ -60278,6 +60283,9 @@ var require_requests_offline = __commonJS({
60278
60283
  sessionConcurrency: -1,
60279
60284
  componentConcurrency: -1
60280
60285
  // unlimited
60286
+ },
60287
+ scmSettings: {
60288
+ isFallbackBranchListEnabled: false
60281
60289
  }
60282
60290
  };
60283
60291
  }
@@ -67327,38 +67335,50 @@ var require_heartbeat = __commonJS({
67327
67335
  const logger = mainLogger.extend(mainLogger, { tags: [`heartbeat-base-${utils34.general.shortid()}`] });
67328
67336
  function stopHeartbeats() {
67329
67337
  logger.log("Stopping heartbeats");
67330
- isRunning = false;
67331
67338
  if (intervalId) {
67332
67339
  clearInterval(intervalId);
67333
67340
  intervalId = void 0;
67341
+ isRunning = false;
67342
+ processId = utils34.general.guid();
67343
+ logger.log("Generated new processId after expiration:", processId);
67334
67344
  }
67335
67345
  }
67336
67346
  async function startHeartbeats(settings) {
67337
- if (!isRunning) {
67338
- processId = utils34.general.guid();
67339
- isRunning = true;
67340
- logger.log("Starting heartbeats with new processId:", processId);
67341
- await requests.sendHeartbeat({ settings: { ...settings, processId }, logger }).catch((error) => {
67342
- logger.error("Failed to send heartbeat:", error);
67343
- });
67344
- intervalId = setIntervalBrowser(async () => {
67345
- try {
67346
- await requests.sendHeartbeat({ settings: { ...settings, processId }, logger });
67347
- } catch (error) {
67348
- if (error.message === "This process is expired. All its sessions were abandoned") {
67349
- stopHeartbeats();
67350
- }
67347
+ await requests.sendHeartbeat({ settings: { ...settings, processId }, logger }).catch((error) => {
67348
+ logger.error("Failed to send heartbeat:", error);
67349
+ });
67350
+ intervalId = setIntervalBrowser(async () => {
67351
+ try {
67352
+ await requests.sendHeartbeat({ settings: { ...settings, processId }, logger });
67353
+ } catch (error) {
67354
+ if (error.message === "This process is expired. All its sessions were abandoned") {
67355
+ logger.log("Heartbeat process expired on server");
67356
+ stopHeartbeats();
67351
67357
  }
67352
- }, settings.interval);
67353
- intervalId.unref();
67354
- }
67358
+ }
67359
+ }, settings.interval);
67360
+ intervalId.unref();
67355
67361
  }
67356
67362
  function acquire(settings) {
67357
67363
  refCount++;
67358
67364
  logger.log("Heartbeat acquired, refCount:", refCount);
67359
- void startHeartbeats(settings).catch((error) => {
67360
- logger.error("Failed to start heartbeats:", error);
67361
- });
67365
+ if (!isRunning) {
67366
+ if (settings.processId && settings.processId === processId) {
67367
+ logger.log("Starting heartbeats with provided processId:", processId);
67368
+ } else if (!settings.processId || settings.processId !== processId) {
67369
+ if (settings.processId && settings.processId !== processId) {
67370
+ logger.log("Provided processId differs from current, using current processId:", processId);
67371
+ } else {
67372
+ processId = utils34.general.guid();
67373
+ logger.log("Starting heartbeats with new processId:", processId);
67374
+ }
67375
+ }
67376
+ isRunning = true;
67377
+ void startHeartbeats(settings).catch((error) => {
67378
+ logger.error("Failed to start heartbeats:", error);
67379
+ });
67380
+ }
67381
+ return processId;
67362
67382
  }
67363
67383
  function release() {
67364
67384
  if (refCount > 0) {
@@ -119577,12 +119597,29 @@ var require_extract_git_info = __commonJS({
119577
119597
  return mod && mod.__esModule ? mod : { "default": mod };
119578
119598
  };
119579
119599
  Object.defineProperty(exports, "__esModule", { value: true });
119580
- exports.isISODate = exports.extractBranchingTimestamp = exports.extractBuildIdFromCI = exports.extractGitRepo = exports.extractGitBranch = exports.extractLatestCommitInfo = exports.cacheKey = void 0;
119600
+ exports.extractBranchLookupFallbackList = exports.isISODate = exports.extractBranchingTimestamp = exports.extractBuildIdFromCI = exports.extractGitRepo = exports.extractGitBranch = exports.extractLatestCommitInfo = exports.getPrimaryRemoteName = exports.cacheKey = void 0;
119581
119601
  var utils34 = __importStar(require_browser3());
119582
119602
  var fs_1 = __importDefault((init_fs(), __toCommonJS(fs_exports)));
119583
119603
  var path_1 = __importDefault(require_path_browserify());
119584
119604
  var logger_1 = require_browser6();
119605
+ var isDebugMode = () => process.env.RUNNER_DEBUG === "1";
119585
119606
  exports.cacheKey = "default";
119607
+ exports.getPrimaryRemoteName = utils34.general.cachify(async function({ execOptions, logger = (0, logger_1.makeLogger)() }) {
119608
+ const result = await executeWithLog("git remote show", { execOptions, logger });
119609
+ if (result.stderr) {
119610
+ logger.log(`Error during extracting remotes from git`, result.stderr);
119611
+ return "origin";
119612
+ }
119613
+ const remotes = result.stdout.trim().split(/\s+/);
119614
+ const remote = remotes.includes("origin") ? "origin" : remotes[0] || "origin";
119615
+ logger.log(`Primary remote name: ${remote}`);
119616
+ return remote;
119617
+ }, (args) => {
119618
+ var _a;
119619
+ return {
119620
+ cwd: (_a = args[0].execOptions) === null || _a === void 0 ? void 0 : _a.cwd
119621
+ };
119622
+ });
119586
119623
  exports.extractLatestCommitInfo = utils34.general.cachify(async function({ execOptions, logger = (0, logger_1.makeLogger)() }) {
119587
119624
  let result;
119588
119625
  try {
@@ -119607,7 +119644,8 @@ var require_extract_git_info = __commonJS({
119607
119644
  async function extractGithubPullRequestLastCommitSha() {
119608
119645
  var _a, _b, _c;
119609
119646
  if (((_a = process.env.GITHUB_EVENT_NAME) === null || _a === void 0 ? void 0 : _a.startsWith("pull_request")) && process.env.GITHUB_EVENT_PATH) {
119610
- await executeWithLog(`git fetch origin --depth=2`, { execOptions, logger });
119647
+ const remoteName = await (0, exports.getPrimaryRemoteName)({ execOptions, logger });
119648
+ await executeWithLog(`git fetch ${remoteName} --depth=2`, { execOptions, logger });
119611
119649
  const event = await fs_1.default.promises.readFile(process.env.GITHUB_EVENT_PATH, "utf-8").then(JSON.parse);
119612
119650
  return (_c = (_b = event === null || event === void 0 ? void 0 : event.pull_request) === null || _b === void 0 ? void 0 : _b.head) === null || _c === void 0 ? void 0 : _c.sha;
119613
119651
  }
@@ -119628,7 +119666,10 @@ var require_extract_git_info = __commonJS({
119628
119666
  logger.log(`Extracted current git branch: "${branch}"`);
119629
119667
  return branch;
119630
119668
  }
119631
- }, () => exports.cacheKey);
119669
+ }, (args) => {
119670
+ var _a, _b, _c;
119671
+ return { cwd: (_b = (_a = args[0]) === null || _a === void 0 ? void 0 : _a.execOptions) === null || _b === void 0 ? void 0 : _b.cwd, ignoreGitBranching: (_c = args[0]) === null || _c === void 0 ? void 0 : _c.ignoreGitBranching };
119672
+ });
119632
119673
  exports.extractGitRepo = utils34.general.cachify(async function({ execOptions, logger = (0, logger_1.makeLogger)() }) {
119633
119674
  const remotes = await extractRemotes();
119634
119675
  logger.log(`Extracted remotes from git: ${remotes}`);
@@ -119668,21 +119709,40 @@ var require_extract_git_info = __commonJS({
119668
119709
  exports.extractBranchingTimestamp = utils34.general.cachify(async function({ branchName, parentBranchName, execOptions, logger = (0, logger_1.makeLogger)() }) {
119669
119710
  var _a;
119670
119711
  logger = logger.extend({ tags: [`extract-branching-timestamp-${utils34.general.shortid()}`] });
119712
+ const remoteName = await (0, exports.getPrimaryRemoteName)({ execOptions, logger });
119671
119713
  const command = `HASH=$(git merge-base ${branchName} ${parentBranchName}) && git show -q --format=%aI $HASH`;
119672
119714
  let result = await executeWithLog(command, { execOptions, logger });
119673
- for (let i = 0; i < 2; i++) {
119674
- if (result.stderr) {
119675
- const [, missingBranch] = (_a = result.stderr.match(/Not a valid object name ([^\s]+)/)) !== null && _a !== void 0 ? _a : [];
119676
- if (missingBranch) {
119677
- result = await executeWithLog(`git fetch origin ${missingBranch}:${missingBranch} && ${command}`, {
119678
- execOptions,
119679
- logger
119680
- });
119715
+ if (result.stderr) {
119716
+ const commandWithRemoteRefs = `HASH=$(git merge-base ${remoteName}/${branchName} ${remoteName}/${parentBranchName}) && git show -q --format=%aI $HASH`;
119717
+ result = await executeWithLog(commandWithRemoteRefs, { execOptions, logger });
119718
+ }
119719
+ if (result.stderr) {
119720
+ const remoteBranches = await getAllRemoteBranches({ execOptions, logger });
119721
+ for (let i = 0; i < 2; i++) {
119722
+ if (result.stderr) {
119723
+ const [, missingBranch] = (_a = result.stderr.match(/Not a valid object name ([^\s]+)/)) !== null && _a !== void 0 ? _a : [];
119724
+ if (missingBranch) {
119725
+ const normalizedBranchName = missingBranch.replace(new RegExp(`^${remoteName}/`), "");
119726
+ if (!remoteBranches.has(normalizedBranchName)) {
119727
+ logger.log(`Branch ${missingBranch} not found on remote, skipping fetch`);
119728
+ return void 0;
119729
+ }
119730
+ logger.log(`Fetching missing branch ${missingBranch} from remote`);
119731
+ const command2 = `HASH=$(git merge-base ${branchName} ${parentBranchName}) && git show -q --format=%aI $HASH`;
119732
+ result = await executeWithLog(`git fetch ${remoteName} ${normalizedBranchName}:${normalizedBranchName} --filter=tree:0 && ${command2}`, {
119733
+ execOptions,
119734
+ logger
119735
+ });
119736
+ }
119681
119737
  }
119682
119738
  }
119683
119739
  }
119684
119740
  if (!result.stdout) {
119685
- result = await executeWithLog(`git fetch origin --unshallow && ${command}`, { execOptions, logger });
119741
+ const command2 = `HASH=$(git merge-base ${branchName} ${parentBranchName}) && git show -q --format=%aI $HASH`;
119742
+ result = await executeWithLog(`git fetch ${remoteName} --unshallow --filter=tree:0 && ${command2}`, {
119743
+ execOptions,
119744
+ logger
119745
+ });
119686
119746
  }
119687
119747
  const timestamp = result.stdout.replace(/\s/g, "");
119688
119748
  if (isISODate2(timestamp)) {
@@ -119691,7 +119751,14 @@ var require_extract_git_info = __commonJS({
119691
119751
  } else {
119692
119752
  logger.log(`Error during extracting merge timestamp: git branching timestamp is an invalid ISO date string: ${timestamp}. stderr: ${result.stderr}, stdout: ${result.stdout}`);
119693
119753
  }
119694
- }, () => exports.cacheKey);
119754
+ }, (args) => {
119755
+ var _a;
119756
+ return {
119757
+ branchName: args[0].branchName,
119758
+ parentBranchName: args[0].parentBranchName,
119759
+ cwd: (_a = args[0].execOptions) === null || _a === void 0 ? void 0 : _a.cwd
119760
+ };
119761
+ });
119695
119762
  async function executeWithLog(command, { execOptions, logger = (0, logger_1.makeLogger)() } = {
119696
119763
  execOptions: {},
119697
119764
  logger: (0, logger_1.makeLogger)()
@@ -119707,6 +119774,365 @@ var require_extract_git_info = __commonJS({
119707
119774
  return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\+\d{2}:\d{2})?/.test(str);
119708
119775
  }
119709
119776
  exports.isISODate = isISODate2;
119777
+ async function parallelWithLimit(items, concurrency, fn) {
119778
+ const results = [];
119779
+ for (let i = 0; i < items.length; i += concurrency) {
119780
+ const batch = items.slice(i, i + concurrency);
119781
+ const batchResults = await Promise.all(batch.map(fn));
119782
+ results.push(...batchResults);
119783
+ }
119784
+ return results;
119785
+ }
119786
+ var getAllRemoteBranches = utils34.general.cachify(
119787
+ async function({ execOptions, logger }) {
119788
+ var _a;
119789
+ if (isDebugMode()) {
119790
+ logger.log("[getAllRemoteBranches] Starting git ls-remote to fetch all remote branches...");
119791
+ logger.log("[getAllRemoteBranches] execOptions.cwd:", (execOptions === null || execOptions === void 0 ? void 0 : execOptions.cwd) || "undefined");
119792
+ }
119793
+ try {
119794
+ const remoteName = await (0, exports.getPrimaryRemoteName)({ execOptions, logger });
119795
+ const startTime = Date.now();
119796
+ const result = await executeWithLog(`git ls-remote --heads ${remoteName}`, {
119797
+ execOptions,
119798
+ logger
119799
+ });
119800
+ if (isDebugMode()) {
119801
+ logger.log(`[getAllRemoteBranches] git ls-remote completed in ${Date.now() - startTime}ms`);
119802
+ logger.log("[getAllRemoteBranches] stdout length:", ((_a = result.stdout) === null || _a === void 0 ? void 0 : _a.length) || 0);
119803
+ logger.log("[getAllRemoteBranches] stderr:", result.stderr || "(empty)");
119804
+ logger.log("[getAllRemoteBranches] exit code:", result.code);
119805
+ }
119806
+ const branches = /* @__PURE__ */ new Set();
119807
+ if (result.stdout.trim()) {
119808
+ const lines = result.stdout.split("\n");
119809
+ if (isDebugMode()) {
119810
+ logger.log(`[getAllRemoteBranches] Processing ${lines.length} lines from git ls-remote output`);
119811
+ }
119812
+ lines.forEach((line, index) => {
119813
+ const match = line.match(/refs\/heads\/(.+)$/);
119814
+ if (match) {
119815
+ const branchName = match[1];
119816
+ branches.add(branchName);
119817
+ if (isDebugMode() && index < 5) {
119818
+ logger.log(`[getAllRemoteBranches] Found branch: ${branchName}`);
119819
+ }
119820
+ }
119821
+ });
119822
+ if (isDebugMode() && lines.length > 5) {
119823
+ logger.log(`[getAllRemoteBranches] ... and ${lines.length - 5} more branches`);
119824
+ }
119825
+ } else {
119826
+ logger.log("[getAllRemoteBranches] WARNING: git ls-remote returned empty output");
119827
+ }
119828
+ logger.log(`[getAllRemoteBranches] \u2713 Found ${branches.size} remote branches via git ls-remote`);
119829
+ if (isDebugMode()) {
119830
+ const branchList = Array.from(branches).slice(0, 20);
119831
+ logger.log("[getAllRemoteBranches] Branch list:", branchList.join(", ") + (branches.size > 20 ? ` ... and ${branches.size - 20} more` : ""));
119832
+ }
119833
+ return branches;
119834
+ } catch (err) {
119835
+ logger.log("[getAllRemoteBranches] \u2717 ERROR: Could not fetch remote branches list");
119836
+ logger.log("[getAllRemoteBranches] Error details:", err);
119837
+ logger.log("[getAllRemoteBranches] Error type:", typeof err);
119838
+ if (err instanceof Error) {
119839
+ logger.log("[getAllRemoteBranches] Error message:", err.message);
119840
+ logger.log("[getAllRemoteBranches] Error stack:", err.stack);
119841
+ }
119842
+ return /* @__PURE__ */ new Set();
119843
+ }
119844
+ },
119845
+ // Custom cache key generator - only cache by cwd, not by logger instance
119846
+ (args) => {
119847
+ var _a;
119848
+ return { cwd: (_a = args[0].execOptions) === null || _a === void 0 ? void 0 : _a.cwd };
119849
+ },
119850
+ 5 * 60 * 1e3
119851
+ );
119852
+ async function executeFetchStrategy(isShallow, execOptions, logger) {
119853
+ const remoteName = await (0, exports.getPrimaryRemoteName)({ execOptions, logger });
119854
+ if (isShallow) {
119855
+ logger.log(`Shallow repository detected, unshallowing to enable topology discovery...`);
119856
+ await executeWithLog(`git fetch ${remoteName} --unshallow --filter=tree:0`, {
119857
+ execOptions,
119858
+ logger
119859
+ });
119860
+ logger.log(`Repository unshallowed successfully`);
119861
+ } else {
119862
+ logger.log(`Non-shallow clone detected, fetching all remote branches for topology discovery...`);
119863
+ await executeWithLog(`git fetch ${remoteName} --filter=tree:0`, {
119864
+ execOptions,
119865
+ logger
119866
+ });
119867
+ logger.log(`All remote branches fetched successfully`);
119868
+ }
119869
+ }
119870
+ async function ensureRemoteBranchesAvailable(branchName, isShallow, execOptions, logger) {
119871
+ try {
119872
+ const remoteName = await (0, exports.getPrimaryRemoteName)({ execOptions, logger });
119873
+ const symbolicResult = await executeWithLog(`git rev-parse --abbrev-ref ${branchName}@{upstream} `, {
119874
+ execOptions,
119875
+ logger
119876
+ });
119877
+ const isRemoteBranch = symbolicResult.stdout.trim().startsWith(`${remoteName}/`);
119878
+ if (!isRemoteBranch) {
119879
+ return;
119880
+ }
119881
+ logger.log(`Detected remote branch scenario, fetching ancestor branches...`);
119882
+ await executeWithLog(`git config remote.${remoteName}.fetch "+refs/heads/*:refs/remotes/${remoteName}/*"`, {
119883
+ execOptions,
119884
+ logger
119885
+ });
119886
+ await executeFetchStrategy(isShallow, execOptions, logger);
119887
+ logger.log(`Remote branches fetched successfully for complete ancestor check`);
119888
+ } catch (err) {
119889
+ logger.log("Note: Could not determine if branch is remote, continuing with topology discovery", err);
119890
+ }
119891
+ }
119892
+ exports.extractBranchLookupFallbackList = utils34.general.cachify(async function({ gitBranchName, execOptions, logger = (0, logger_1.makeLogger)(), enableShallowClone = true }) {
119893
+ const functionStartTime = Date.now();
119894
+ logger = logger.extend({ tags: [`extract-branch-fallback-list-${utils34.general.shortid()}`] });
119895
+ logger.log(`[PERF] extractBranchLookupFallbackList started for branch: ${gitBranchName}`);
119896
+ try {
119897
+ const shallowCheckStartTime = Date.now();
119898
+ const shallowCheckResult = await executeWithLog("git rev-parse --is-shallow-repository", {
119899
+ execOptions,
119900
+ logger
119901
+ });
119902
+ logger.log(`[PERF] Shallow check took ${Date.now() - shallowCheckStartTime}ms`);
119903
+ const isShallow = shallowCheckResult.stdout.trim() === "true";
119904
+ if (!enableShallowClone && isShallow) {
119905
+ logger.log("Shallow clone detected and APPLITOOLS_SKIP_BRANCH_LOOKUP_IN_SHALLOW_CLONE is enabled, skipping branch lookup");
119906
+ return void 0;
119907
+ }
119908
+ const ensureRemoteStartTime = Date.now();
119909
+ await ensureRemoteBranchesAvailable(gitBranchName, isShallow, execOptions, logger);
119910
+ logger.log(`[PERF] ensureRemoteBranchesAvailable took ${Date.now() - ensureRemoteStartTime}ms`);
119911
+ const topologyStartTime = Date.now();
119912
+ logger.log(`Discovering ancestor branches using git topology for ${gitBranchName}...`);
119913
+ const remoteName = await (0, exports.getPrimaryRemoteName)({ execOptions, logger });
119914
+ const logResult = await executeWithLog(`git log --first-parent --simplify-by-decoration --format="%D" ${gitBranchName}`, { execOptions, logger });
119915
+ const foundBranches = /* @__PURE__ */ new Set();
119916
+ if (!logResult.stderr && logResult.stdout.trim()) {
119917
+ const rawLines = logResult.stdout.split("\n");
119918
+ for (const line of rawLines) {
119919
+ const refs = line.split(",").map((r) => r.trim());
119920
+ for (const ref of refs) {
119921
+ if (ref.includes("tag:"))
119922
+ continue;
119923
+ const cleanRef = ref.replace("HEAD -> ", "").replace(new RegExp(`^${remoteName}/`), "");
119924
+ if (cleanRef && cleanRef !== gitBranchName && cleanRef !== "HEAD")
119925
+ foundBranches.add(cleanRef);
119926
+ }
119927
+ }
119928
+ }
119929
+ let allBranches = Array.from(foundBranches);
119930
+ logger.log(`[PERF] Topology discovery took ${Date.now() - topologyStartTime}ms`);
119931
+ if (isDebugMode()) {
119932
+ logger.log(`Topology discovered ${allBranches.length} potential ancestor branches: ${allBranches.join(", ")}`);
119933
+ } else {
119934
+ logger.log(`Topology discovered ${allBranches.length} potential ancestor branches`);
119935
+ }
119936
+ const remoteBranchesStartTime = Date.now();
119937
+ if (isDebugMode()) {
119938
+ logger.log("[Remote Filtering] ========================================");
119939
+ logger.log("[Remote Filtering] Starting remote branch filtering...");
119940
+ logger.log("[Remote Filtering] Branches to check:", allBranches.join(", "));
119941
+ logger.log("[Remote Filtering] Number of branches before filtering:", allBranches.length);
119942
+ }
119943
+ try {
119944
+ if (isDebugMode()) {
119945
+ logger.log("[Remote Filtering] Calling getAllRemoteBranches...");
119946
+ }
119947
+ const remoteBranches = await getAllRemoteBranches({ execOptions, logger });
119948
+ if (isDebugMode()) {
119949
+ logger.log("[Remote Filtering] getAllRemoteBranches returned");
119950
+ logger.log("[Remote Filtering] Remote branches size:", remoteBranches.size);
119951
+ const remoteBranchList = Array.from(remoteBranches).slice(0, 20);
119952
+ logger.log("[Remote Filtering] Remote branches:", remoteBranchList.join(", ") + (remoteBranches.size > 20 ? ` ... and ${remoteBranches.size - 20} more` : ""));
119953
+ }
119954
+ if (remoteBranches.size > 0) {
119955
+ const beforeFilter = allBranches.length;
119956
+ if (isDebugMode()) {
119957
+ logger.log("[Remote Filtering] Filtering branches against remote...");
119958
+ allBranches.forEach((branch) => {
119959
+ const exists = remoteBranches.has(branch);
119960
+ logger.log(`[Remote Filtering] Branch "${branch}": ${exists ? "\u2713 EXISTS" : "\u2717 MISSING"} on remote`);
119961
+ });
119962
+ }
119963
+ allBranches = allBranches.filter((branch) => remoteBranches.has(branch));
119964
+ const filtered = beforeFilter - allBranches.length;
119965
+ if (isDebugMode()) {
119966
+ logger.log("[Remote Filtering] Branches after filtering:", allBranches.join(", "));
119967
+ logger.log("[Remote Filtering] Number of branches after filtering:", allBranches.length);
119968
+ }
119969
+ if (filtered > 0) {
119970
+ logger.log(`[Remote Filtering] \u2713 Filtered out ${filtered} branches not found on remote`);
119971
+ } else if (isDebugMode()) {
119972
+ logger.log("[Remote Filtering] No branches were filtered out (all exist on remote)");
119973
+ }
119974
+ } else {
119975
+ logger.log("[Remote Filtering] WARNING: No remote branches found, skipping filter");
119976
+ }
119977
+ } catch (err) {
119978
+ logger.log("[Remote Filtering] \u2717 ERROR: Could not filter by remote branches");
119979
+ if (isDebugMode()) {
119980
+ logger.log("[Remote Filtering] Error details:", err);
119981
+ logger.log("[Remote Filtering] Continuing with all discovered branches");
119982
+ }
119983
+ }
119984
+ logger.log(`[Remote Filtering] [PERF] Remote branch filtering took ${Date.now() - remoteBranchesStartTime}ms`);
119985
+ if (isDebugMode()) {
119986
+ logger.log("[Remote Filtering] ========================================");
119987
+ }
119988
+ const filteringStartTime = Date.now();
119989
+ logger.log(`Filtering out sibling branches to keep only true ancestors...`);
119990
+ const ANCESTOR_CHECK_CONCURRENCY = 10;
119991
+ const ancestorChecks = await parallelWithLimit(allBranches, ANCESTOR_CHECK_CONCURRENCY, async (candidateBranch) => {
119992
+ try {
119993
+ const isAncestorResult = await executeWithLog(`git merge-base --is-ancestor ${candidateBranch} ${gitBranchName}`, {
119994
+ execOptions,
119995
+ logger
119996
+ });
119997
+ if (isAncestorResult.code === 0) {
119998
+ logger.log(`\u2713 ${candidateBranch} is a true ancestor`);
119999
+ return { branch: candidateBranch, isAncestor: true };
120000
+ } else if (isAncestorResult.code === 1) {
120001
+ logger.log(`\u2717 ${candidateBranch} is a sibling, not an ancestor`);
120002
+ return { branch: candidateBranch, isAncestor: false };
120003
+ } else {
120004
+ logger.log(`\u26A0 Could not determine if ${candidateBranch} is an ancestor (exit code: ${isAncestorResult.code}), including it to be safe`);
120005
+ return { branch: candidateBranch, isAncestor: true };
120006
+ }
120007
+ } catch (err) {
120008
+ logger.log(`Error checking if ${candidateBranch} is a true ancestor:`, err);
120009
+ return { branch: candidateBranch, isAncestor: true };
120010
+ }
120011
+ });
120012
+ const trueAncestors = ancestorChecks.filter((result) => result.isAncestor).map((result) => result.branch);
120013
+ logger.log(`[PERF] Sibling filtering took ${Date.now() - filteringStartTime}ms`);
120014
+ logger.log(`Filtered to ${trueAncestors.length} true ancestors: ${trueAncestors.join(", ")}`);
120015
+ const timestampStartTime = Date.now();
120016
+ const branchesWithTimestamps = await Promise.all(trueAncestors.map(async (ancestorBranch) => {
120017
+ const timestamp = await (0, exports.extractBranchingTimestamp)({
120018
+ branchName: gitBranchName,
120019
+ parentBranchName: ancestorBranch,
120020
+ execOptions,
120021
+ logger
120022
+ });
120023
+ return timestamp ? { branchName: ancestorBranch, latestViableTimestamp: timestamp } : null;
120024
+ }));
120025
+ logger.log(`[PERF] Timestamp extraction took ${Date.now() - timestampStartTime}ms`);
120026
+ const validBranches = branchesWithTimestamps.filter((item) => item !== null).sort((a, b) => {
120027
+ const timeDiff = new Date(b.latestViableTimestamp).getTime() - new Date(a.latestViableTimestamp).getTime();
120028
+ if (timeDiff === 0) {
120029
+ return a.branchName.localeCompare(b.branchName);
120030
+ }
120031
+ return timeDiff;
120032
+ });
120033
+ try {
120034
+ const firstCommitResult = await executeWithLog("git rev-list --max-parents=0 HEAD", {
120035
+ execOptions,
120036
+ logger
120037
+ });
120038
+ if (firstCommitResult.stdout.trim()) {
120039
+ const firstCommitHash = firstCommitResult.stdout.trim().split("\n")[0];
120040
+ logger.log(`Found root commit: ${firstCommitHash}`);
120041
+ let branchesContainingRootResult = await executeWithLog(`git branch -r --contains ${firstCommitHash}`, {
120042
+ execOptions,
120043
+ logger
120044
+ });
120045
+ if (!branchesContainingRootResult.stdout.trim()) {
120046
+ logger.log("No remote branches contain root commit, trying local branches");
120047
+ branchesContainingRootResult = await executeWithLog(`git branch --contains ${firstCommitHash}`, {
120048
+ execOptions,
120049
+ logger
120050
+ });
120051
+ }
120052
+ if (branchesContainingRootResult.stdout.trim()) {
120053
+ const branchesContainingRoot = branchesContainingRootResult.stdout.split("\n").map((line) => line.trim()).filter((line) => line && !line.includes("->") && !line.includes("HEAD")).map((line) => line.replace(new RegExp(`^${remoteName}/`), "").replace(/^\* /, ""));
120054
+ const commonRootNames = ["main", "master", "develop", "trunk"];
120055
+ let rootBranch = null;
120056
+ for (const commonName of commonRootNames) {
120057
+ if (branchesContainingRoot.includes(commonName)) {
120058
+ rootBranch = commonName;
120059
+ logger.log(`Found root branch using common name: ${rootBranch}`);
120060
+ break;
120061
+ }
120062
+ }
120063
+ if (!rootBranch && branchesContainingRoot.length > 0) {
120064
+ const branchTimestamps = await Promise.all(branchesContainingRoot.slice(0, 10).map(async (branch) => {
120065
+ try {
120066
+ const branchTimestampResult = await executeWithLog(`git log -1 --format=%aI ${branch}`, {
120067
+ execOptions,
120068
+ logger
120069
+ });
120070
+ return {
120071
+ branch,
120072
+ timestamp: branchTimestampResult.stdout.trim()
120073
+ };
120074
+ } catch {
120075
+ return null;
120076
+ }
120077
+ }));
120078
+ const validTimestamps = branchTimestamps.filter((item) => item !== null && isISODate2(item.timestamp)).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
120079
+ if (validTimestamps.length > 0) {
120080
+ rootBranch = validTimestamps[0].branch;
120081
+ logger.log(`Found root branch by oldest timestamp: ${rootBranch}`);
120082
+ }
120083
+ }
120084
+ if (rootBranch && rootBranch !== gitBranchName) {
120085
+ const alreadyIncluded = validBranches.some((b) => b.branchName === rootBranch);
120086
+ if (!alreadyIncluded) {
120087
+ try {
120088
+ const isAncestorResult = await executeWithLog(`git merge-base --is-ancestor ${rootBranch} ${gitBranchName}`, {
120089
+ execOptions,
120090
+ logger
120091
+ });
120092
+ if (isAncestorResult.code === 0) {
120093
+ logger.log(`Root branch ${rootBranch} is a true ancestor, adding it as final fallback`);
120094
+ const rootTimestamp = await (0, exports.extractBranchingTimestamp)({
120095
+ branchName: gitBranchName,
120096
+ parentBranchName: rootBranch,
120097
+ execOptions,
120098
+ logger
120099
+ });
120100
+ if (rootTimestamp) {
120101
+ validBranches.push({
120102
+ branchName: rootBranch,
120103
+ latestViableTimestamp: rootTimestamp
120104
+ });
120105
+ logger.log(`Added root branch ${rootBranch} with timestamp ${rootTimestamp}`);
120106
+ }
120107
+ } else {
120108
+ logger.log(`Root branch ${rootBranch} is not an ancestor of ${gitBranchName}, skipping`);
120109
+ }
120110
+ } catch (err) {
120111
+ logger.log(`Error checking if root branch ${rootBranch} is an ancestor:`, err);
120112
+ }
120113
+ } else {
120114
+ logger.log(`Root branch ${rootBranch} already included in the list`);
120115
+ }
120116
+ }
120117
+ }
120118
+ }
120119
+ } catch (err) {
120120
+ logger.log("Failed to detect and add root branch, continuing without it", err);
120121
+ }
120122
+ logger.log(`[PERF] extractBranchLookupFallbackList completed in ${Date.now() - functionStartTime}ms total`);
120123
+ logger.log("Successfully extracted branch lookup fallback list", JSON.stringify(validBranches));
120124
+ return validBranches.length > 0 ? validBranches : void 0;
120125
+ } catch (err) {
120126
+ logger.log("Error during extracting branch lookup fallback list", err);
120127
+ return void 0;
120128
+ }
120129
+ }, (args) => {
120130
+ var _a;
120131
+ return {
120132
+ gitBranchName: args[0].gitBranchName,
120133
+ cwd: (_a = args[0].execOptions) === null || _a === void 0 ? void 0 : _a.cwd
120134
+ };
120135
+ });
119710
120136
  }
119711
120137
  });
119712
120138
 
@@ -119772,6 +120198,7 @@ var require_open_eyes4 = __commonJS({
119772
120198
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
119773
120199
  var _1, _2, _3, _4, _5;
119774
120200
  logger = logger.extend(mainLogger, { tags: [`eyes-${type}-${utils34.general.shortid()}`] });
120201
+ logger.log(`Opening Eyes in directory "${cwd}"`);
119775
120202
  const settings = { environments: config === null || config === void 0 ? void 0 : config.check.environments, ...config === null || config === void 0 ? void 0 : config.open, ...openSettings };
119776
120203
  const eyesServerSettings = (0, populate_eyes_server_settings_1.populateEyesServerSettings)(settings);
119777
120204
  logger.mask(eyesServerSettings.apiKey);
@@ -119831,6 +120258,7 @@ var require_open_eyes4 = __commonJS({
119831
120258
  }
119832
120259
  }
119833
120260
  const account = await core.getAccountInfo({ settings: eyesServerSettings, logger });
120261
+ await handleBranchFallbackList(account, settings, logger);
119834
120262
  const componentConcurrency = account.serverConcurrency.componentConcurrency;
119835
120263
  settings.sessionConcurrency = handleConcurrency({
119836
120264
  userConcurrency: concurrency,
@@ -119914,6 +120342,31 @@ var require_open_eyes4 = __commonJS({
119914
120342
  }
119915
120343
  }
119916
120344
  };
120345
+ async function handleBranchFallbackList(account, settings, logger) {
120346
+ var _a, _b;
120347
+ const scm = getScmSettings(account.scmSettings);
120348
+ if (!settings.ignoreGitBranching && scm.lookupFallbackList) {
120349
+ logger.log(`Extracting git branch name, ${cwd}`);
120350
+ settings.gitBranchName = await (0, extract_git_info_1.extractGitBranch)({
120351
+ execOptions: { cwd },
120352
+ logger,
120353
+ ignoreGitBranching: settings.ignoreGitBranching
120354
+ });
120355
+ logger.log(`Extracted git branch name: ${settings.gitBranchName}`);
120356
+ (_a = settings.branchName) !== null && _a !== void 0 ? _a : settings.branchName = settings.gitBranchName;
120357
+ } else {
120358
+ logger.log(`Skipping extraction of git branch name as ignoreGitBranching is set to true`);
120359
+ }
120360
+ if (settings.gitBranchName && !settings.ignoreGitBranching && scm.lookupFallbackList) {
120361
+ logger.log(`Extracting branch lookup fallback list for branch "${settings.gitBranchName}, ${cwd}"`);
120362
+ settings.branchLookupFallbackList = await (0, extract_git_info_1.extractBranchLookupFallbackList)({
120363
+ gitBranchName: `${settings.gitBranchName}`,
120364
+ enableShallowClone: (_b = scm.enableShallowClone) !== null && _b !== void 0 ? _b : false,
120365
+ execOptions: { cwd },
120366
+ logger
120367
+ });
120368
+ }
120369
+ }
119917
120370
  }
119918
120371
  exports.makeOpenEyes = makeOpenEyes;
119919
120372
  function refineMissingApiKeyException(error, sdk) {
@@ -119986,6 +120439,23 @@ var require_open_eyes4 = __commonJS({
119986
120439
  function isRealBatchId(batchId) {
119987
120440
  return !!batchId && !batchId.startsWith("generated");
119988
120441
  }
120442
+ function getScmSettings(scmSettings) {
120443
+ var _a, _b;
120444
+ let enableShallowClone = (_a = scmSettings.isShallowCloneEnabled) !== null && _a !== void 0 ? _a : true;
120445
+ let lookupFallbackList = (_b = scmSettings.isFallbackBranchListEnabled) !== null && _b !== void 0 ? _b : false;
120446
+ const envLookupFallbackList = utils34.general.getEnvValue("ENABLE_BRANCH_LOOKUP_FALLBACK_LIST", "boolean");
120447
+ const envShallowClone = utils34.general.getEnvValue("SKIP_BRANCH_LOOKUP_IN_SHALLOW_CLONE", "boolean");
120448
+ if (envLookupFallbackList !== void 0) {
120449
+ lookupFallbackList = envLookupFallbackList;
120450
+ }
120451
+ if (envShallowClone !== void 0) {
120452
+ enableShallowClone = !envShallowClone;
120453
+ }
120454
+ return {
120455
+ lookupFallbackList,
120456
+ enableShallowClone
120457
+ };
120458
+ }
119989
120459
  function handleConcurrency({ userConcurrency, componentConcurrency, useServerConcurrency, shouldPrintConcurrencyWarning, logger }) {
119990
120460
  let sessionConcurrency;
119991
120461
  if (userConcurrency !== void 0 && userConcurrency <= 0) {
@@ -120497,7 +120967,7 @@ var require_package3 = __commonJS({
120497
120967
  "../core/package.json"(exports, module) {
120498
120968
  module.exports = {
120499
120969
  name: "@applitools/core",
120500
- version: "4.53.2",
120970
+ version: "4.54.0",
120501
120971
  homepage: "https://applitools.com",
120502
120972
  bugs: {
120503
120973
  url: "https://github.com/applitools/eyes.sdk.javascript1/issues"
@@ -120548,12 +121018,13 @@ var require_package3 = __commonJS({
120548
121018
  "build:bin": "yarn build && sea",
120549
121019
  "build:bin:zip": "zip -j ./bin/core.zip $(find ./bin -type f -not -name '*.zip' -not -name '*.tar.gz' | xargs)",
120550
121020
  "build:bin:tgz": "tar -czf ./bin/core.tar.gz $(find ./bin -type f -not -name '*.zip' -not -name '*.tar.gz' | xargs)",
120551
- test: "run --top-level mocha './test/**/*.spec.ts' --exclude './test/bin/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
121021
+ test: "run --top-level mocha './test/**/*.spec.ts' --exclude './test/bin/**' --exclude './test/e2e/mocha-sync/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
120552
121022
  "test:local": "MOCHA_OMIT_TAGS=sauce,browserstack run test",
120553
121023
  "test:sauce": "MOCHA_ONLY_TAGS=sauce,browserstack run test",
120554
121024
  "test:bin": "MOCHA_GROUP=bin run --top-level mocha './test/bin/**/*.spec.ts' --parallel --jobs ${MOCHA_JOBS:-15} --require ./test/mocha-global-setup.js",
120555
- "test:e2e": "MOCHA_GROUP=e2e run --top-level mocha './test/e2e/**/*.spec.ts' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
121025
+ "test:e2e": "MOCHA_GROUP=e2e run --top-level mocha './test/e2e/**/*.spec.ts' --exclude './test/e2e/mocha-sync/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
120556
121026
  "test:it": "MOCHA_GROUP=it run --top-level mocha './test/it/**/*.spec.ts' --require ./test/mocha-global-setup.js",
121027
+ "test:e2e:sync": "MOCHA_GROUP=e2e SYNC=true run --top-level mocha './test/e2e/mocha-sync/**/*.spec.ts' --exit --require ./test/mocha-global-setup.js",
120557
121028
  "test:unit": "MOCHA_GROUP=unit run --top-level mocha './test/unit/**/*.spec.ts' --require ./test/mocha-global-setup.js",
120558
121029
  setup: "run --top-level browsers:setup && run --top-level xvfb:setup",
120559
121030
  "setup:standalone": "sh -c 'yarn chromedriver --port=4444 --verbose &'"
@@ -127412,7 +127883,7 @@ var require_package4 = __commonJS({
127412
127883
  "../eyes/package.json"(exports, module) {
127413
127884
  module.exports = {
127414
127885
  name: "@applitools/eyes",
127415
- version: "1.36.17",
127886
+ version: "1.36.18",
127416
127887
  keywords: [
127417
127888
  "applitools",
127418
127889
  "eyes",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/eyes-browser",
3
- "version": "1.5.17",
3
+ "version": "1.5.18",
4
4
  "keywords": [
5
5
  "applitools",
6
6
  "browser",
@@ -48,7 +48,7 @@
48
48
  "test": "run --top-level mocha './test/**/*.spec.ts'"
49
49
  },
50
50
  "dependencies": {
51
- "@applitools/eyes": "1.36.17"
51
+ "@applitools/eyes": "1.36.18"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^12.20.55",