@checksum-ai/runtime 1.1.49 → 1.1.50-beta

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/checksumlib.js CHANGED
@@ -7509,6 +7509,9 @@
7509
7509
  return null;
7510
7510
  }, "closestExtended");
7511
7511
  var childNodesExtended = /* @__PURE__ */ __name((node2, { flattenShadowRootChildren = false } = {}) => {
7512
+ if (!node2) {
7513
+ return [];
7514
+ }
7512
7515
  let childNodes2 = Array.from(node2.childNodes);
7513
7516
  if (node2.nodeType === Node.ELEMENT_NODE) {
7514
7517
  const element = node2;
@@ -7579,7 +7582,12 @@
7579
7582
  }
7580
7583
  return null;
7581
7584
  }, "parentElementExtended");
7582
- var serializeElement = /* @__PURE__ */ __name((element, { includeComments = true, includeCDATA = true, trim = true } = {}) => {
7585
+ var serializeElement = /* @__PURE__ */ __name((element, {
7586
+ includeComments = true,
7587
+ includeCDATA = true,
7588
+ trim = true,
7589
+ limitAttributeLength = false
7590
+ } = {}) => {
7583
7591
  if (!element) {
7584
7592
  return "";
7585
7593
  }
@@ -7588,7 +7596,7 @@
7588
7596
  case Node.ELEMENT_NODE:
7589
7597
  return serializeElementNode2(node2);
7590
7598
  case Node.TEXT_NODE:
7591
- return trim ? node2.nodeValue.trim() : node2.nodeValue;
7599
+ return node2.nodeValue;
7592
7600
  case Node.CDATA_SECTION_NODE:
7593
7601
  return includeCDATA ? `<![CDATA[${node2.nodeValue}]]>` : "";
7594
7602
  case Node.COMMENT_NODE:
@@ -7615,7 +7623,11 @@
7615
7623
  let tagName = element2.tagName.toLowerCase();
7616
7624
  let serialized = `<${tagName}`;
7617
7625
  Array.from(element2.attributes).forEach((attr) => {
7618
- serialized += ` ${attr.name}="${attr.value}"`;
7626
+ const attributeValue = attr.value.slice(
7627
+ 0,
7628
+ limitAttributeLength ? 100 : void 0
7629
+ );
7630
+ serialized += ` ${attr.name}="${attributeValue}"`;
7619
7631
  });
7620
7632
  serialized += ">";
7621
7633
  if (element2.shadowRoot) {
@@ -32744,6 +32756,39 @@
32744
32756
 
32745
32757
  // src/lib/test-generator/selectors/pw-custom-locator-generator.ts
32746
32758
  var DEBUG_MODE = false;
32759
+ var LocatorChain = class _LocatorChain {
32760
+ constructor(locators) {
32761
+ this.locators = [];
32762
+ this.elements = [];
32763
+ this.locators = locators;
32764
+ }
32765
+ static {
32766
+ __name(this, "LocatorChain");
32767
+ }
32768
+ /**
32769
+ * Check if any of the locators have the same filter values
32770
+ * For now, we only check for hasText filter
32771
+ */
32772
+ hasDuplicateFilters() {
32773
+ const hasTextFilters = this.locators.map((l2) => l2.options?.hasText).filter((value) => value);
32774
+ return hasTextFilters.length !== new Set(hasTextFilters).size;
32775
+ }
32776
+ cloneAndAddCandidate(candidate) {
32777
+ return new _LocatorChain([candidate, ...this.locators.slice()]);
32778
+ }
32779
+ toLocatorCandidate() {
32780
+ return {
32781
+ selector: this.getSelector(),
32782
+ locator: this.getLocator()
32783
+ };
32784
+ }
32785
+ getSelector() {
32786
+ return this.locators.map((l2) => l2.selector).join(" >> ");
32787
+ }
32788
+ getLocator() {
32789
+ return this.locators.map((l2) => l2.locator).join(".");
32790
+ }
32791
+ };
32747
32792
  var PlaywrightCustomLocatorGenerator = class _PlaywrightCustomLocatorGenerator {
32748
32793
  constructor(rootNode = document) {
32749
32794
  this.rootNode = rootNode;
@@ -32763,29 +32808,23 @@
32763
32808
  * @param cssKeyElements array of css key elements to use as selectors
32764
32809
  * @param options
32765
32810
  */
32766
- async generate(element, cssKeyElements = [], {
32767
- /**
32768
- * if element is part of a list - don't limit to 1 element,
32769
- * but verify that all returned elements are part of the same list
32770
- */
32771
- isPartOfListItem = false,
32772
- /**
32773
- * Generate a selector for context element.
32774
- * For context elements we should generate a simplistic selector that will not include text context from the page.
32775
- * For that reason we should not bubble up the DOM tree to find the best selector, but rather use the element itself.
32776
- * It can also return multiple elements.
32777
- */
32778
- isForContextElement = false,
32779
- /**
32780
- * Whether to use text content to generate selectors
32781
- * Useful to turn off when wanting to avoid page context in selectors like user data
32782
- **/
32783
- useTextContent = true,
32784
- /* Whether to add CSS selector from the css-selector-generator library */
32785
- addCSSSelectorGenerator = true,
32786
- /* Whether to heuristiically expand CSS key elements to include more selectors */
32787
- expandCSSKeyElements = true
32788
- } = {}) {
32811
+ async generate(element, cssKeyElements = [], options = {}) {
32812
+ const {
32813
+ isPartOfListItem,
32814
+ isForContextElement,
32815
+ useTextContent,
32816
+ addCSSSelectorGenerator,
32817
+ expandCSSKeyElements,
32818
+ bruteForceMode
32819
+ } = {
32820
+ isPartOfListItem: false,
32821
+ isForContextElement: false,
32822
+ useTextContent: true,
32823
+ addCSSSelectorGenerator: true,
32824
+ expandCSSKeyElements: true,
32825
+ bruteForceMode: false,
32826
+ ...options
32827
+ };
32789
32828
  if (isNodeInstanceOf(element, "HTMLOptionElement")) {
32790
32829
  const select = element.closest("select");
32791
32830
  if (select) {
@@ -32794,10 +32833,9 @@
32794
32833
  });
32795
32834
  }
32796
32835
  }
32797
- this.usedFilters = { hasText: [], has: [] };
32798
32836
  this.targetElement = element;
32799
32837
  this.startTimestamp = Date.now();
32800
- const locatorsCandidates = [];
32838
+ const locatorsChains = [];
32801
32839
  cssKeyElements = cssKeyElements.filter(
32802
32840
  (featureElement) => Array.from(document.querySelectorAll(featureElement.selector)).includes(
32803
32841
  featureElement.element
@@ -32807,70 +32845,78 @@
32807
32845
  this.expandCSSKeyFeatures(element, cssKeyElements);
32808
32846
  }
32809
32847
  while (element && element !== this.rootNode) {
32810
- const elementSelectors = this.getAllLocators(element, cssKeyElements, {
32811
- useTextContent
32812
- });
32848
+ const elementCandidates = this.getAllLocators(
32849
+ element,
32850
+ cssKeyElements,
32851
+ {
32852
+ useTextContent,
32853
+ bruteForceMode
32854
+ }
32855
+ );
32813
32856
  const validLocators = [];
32814
- const testSelector2 = /* @__PURE__ */ __name((selector) => {
32857
+ const testCandidate = /* @__PURE__ */ __name((chain) => {
32858
+ if (chain.hasDuplicateFilters()) {
32859
+ return;
32860
+ }
32815
32861
  try {
32816
- const { elements } = this.getLocatorBase(element).locator(selector);
32817
- if (elements.some((e2) => e2 === this.targetElement)) {
32818
- validLocators.push({
32819
- selector,
32820
- locator: this.getSelectorLocator(selector),
32821
- elements: elements.filter(
32822
- (el) => isInstanceOfHTMLElement(el) || el.constructor.name === this.targetElement.constructor.name
32823
- )
32824
- });
32862
+ const { elements } = this.getLocatorBase(element).locator(
32863
+ chain.getSelector()
32864
+ );
32865
+ if (elements?.some((e2) => e2 === this.targetElement)) {
32866
+ chain.elements = elements.filter(
32867
+ (el) => isInstanceOfHTMLElement(el) || el.constructor.name === this.targetElement.constructor.name
32868
+ ), validLocators.push(chain);
32825
32869
  }
32826
32870
  } catch (error) {
32827
- console.log(error);
32871
+ console.log(error, chain.getSelector());
32828
32872
  }
32829
- }, "testSelector");
32830
- if (!locatorsCandidates.length) {
32831
- elementSelectors.forEach((selector) => testSelector2(selector));
32873
+ }, "testCandidate");
32874
+ if (!locatorsChains.length) {
32875
+ elementCandidates.forEach(
32876
+ (ec) => testCandidate(new LocatorChain([ec]))
32877
+ );
32832
32878
  } else {
32833
- elementSelectors.forEach((elementSelector) => {
32834
- locatorsCandidates.forEach(
32835
- (sc) => testSelector2(`${elementSelector} >> ${sc.selector}`)
32879
+ elementCandidates.forEach((ec) => {
32880
+ locatorsChains.forEach(
32881
+ (sc) => testCandidate(sc.cloneAndAddCandidate(ec))
32836
32882
  );
32837
32883
  });
32838
32884
  }
32839
- if (validLocators.length === 0 && locatorsCandidates.length === 0) {
32885
+ if (validLocators.length === 0 && locatorsChains.length === 0) {
32840
32886
  const selector = element.tagName.toLowerCase();
32841
- locatorsCandidates.push({
32842
- selector,
32843
- locator: this.getSelectorLocator(selector),
32844
- elements: this.getLocatorBase(element).locator(element.tagName.toLowerCase()).elements.filter(
32845
- (el) => isInstanceOfHTMLElement(el)
32846
- )
32847
- });
32887
+ locatorsChains.push(
32888
+ new LocatorChain([
32889
+ {
32890
+ selector,
32891
+ locator: this.getSelectorLocator(selector),
32892
+ elements: this.getLocatorBase(element).locator(element.tagName.toLowerCase()).elements.filter(
32893
+ (el) => isInstanceOfHTMLElement(el)
32894
+ )
32895
+ }
32896
+ ])
32897
+ );
32848
32898
  }
32849
- locatorsCandidates.push(...validLocators);
32899
+ locatorsChains.push(...validLocators);
32850
32900
  if (isForContextElement) {
32851
- return locatorsCandidates.map(({ selector, locator }) => ({
32852
- selector,
32853
- locator
32854
- }));
32901
+ return locatorsChains.map((chain) => chain.toLocatorCandidate());
32855
32902
  }
32856
- if (locatorsCandidates.length > this.MAX_TESTING_SELECTORS || locatorsCandidates.filter((sc) => sc.elements.length === 1).length > this.MAX_SINGLE_ELEMENT_SELECTORS || this.checkTimeout(false)) {
32903
+ if (locatorsChains.length > this.MAX_TESTING_SELECTORS || locatorsChains.filter((sc) => sc.elements.length === 1).length > this.MAX_SINGLE_ELEMENT_SELECTORS || this.checkTimeout(false)) {
32857
32904
  break;
32858
32905
  }
32859
32906
  element = element.parentElement;
32860
32907
  }
32861
32908
  if (isPartOfListItem) {
32862
- const listItemsCandidates = this.filterListItemsSelectors(locatorsCandidates);
32909
+ const listItemsCandidates = this.filterListItemsSelectors(locatorsChains);
32863
32910
  if (listItemsCandidates?.length) {
32864
- return listItemsCandidates.map(({ selector, locator }) => ({
32865
- selector,
32866
- locator,
32911
+ return listItemsCandidates.map((lic) => ({
32912
+ ...lic.toLocatorCandidate(),
32867
32913
  isListSelector: true
32868
32914
  }));
32869
32915
  }
32870
32916
  }
32871
32917
  let filteredCandidates = [];
32872
32918
  try {
32873
- for (const candidate of locatorsCandidates) {
32919
+ for (const candidate of locatorsChains) {
32874
32920
  this.checkTimeout();
32875
32921
  filteredCandidates.push(
32876
32922
  ...candidate.elements.length === 1 ? [candidate] : await this.reduceMultiCandidates(candidate)
@@ -32903,10 +32949,7 @@
32903
32949
  const { selector, locator } = await new PlaywrightElementSelectorGenerator().getSelectorAndLocator(
32904
32950
  this.targetElement
32905
32951
  );
32906
- filteredCandidates.unshift({
32907
- selector,
32908
- locator
32909
- });
32952
+ filteredCandidates.unshift(new LocatorChain([{ selector, locator }]));
32910
32953
  }
32911
32954
  filteredCandidates = filteredCandidates.concat(
32912
32955
  await this.addOptionalParentSelector(
@@ -32915,10 +32958,14 @@
32915
32958
  expandCSSKeyElements
32916
32959
  )
32917
32960
  );
32918
- return filteredCandidates.map(({ selector, locator }) => ({
32919
- selector,
32920
- locator
32921
- }));
32961
+ return Array.from(
32962
+ new Map(
32963
+ filteredCandidates.map((fc) => [
32964
+ fc.toLocatorCandidate().selector,
32965
+ fc.toLocatorCandidate()
32966
+ ])
32967
+ ).values()
32968
+ );
32922
32969
  }
32923
32970
  /**
32924
32971
  * Test if a selector can locate an element
@@ -32986,10 +33033,12 @@
32986
33033
  2e3
32987
33034
  );
32988
33035
  if (selector.length < 50) {
32989
- return {
32990
- selector,
32991
- locator: `locator('${selector}')`
32992
- };
33036
+ return new LocatorChain([
33037
+ {
33038
+ selector,
33039
+ locator: `locator('${selector}')`
33040
+ }
33041
+ ]);
32993
33042
  }
32994
33043
  } catch (error) {
32995
33044
  console.error("Error getting CSS selector", error);
@@ -32999,20 +33048,22 @@
32999
33048
  * If playwright selector for element points at a different element - generate selectors for that element as well
33000
33049
  * i.e - playwright can point at a parent button if element is a child of it
33001
33050
  */
33002
- addOptionalParentSelector(useTextContent, addCSSSelectorGenerator, expandCSSKeyElements) {
33051
+ async addOptionalParentSelector(useTextContent, addCSSSelectorGenerator, expandCSSKeyElements) {
33003
33052
  try {
33004
33053
  const playwright = getElementWindowPlaywright(this.targetElement);
33005
33054
  const playwrightTargetElement = playwright.locator(
33006
33055
  playwright.selector(this.targetElement)
33007
33056
  ).element;
33008
33057
  if (playwrightTargetElement !== this.targetElement && playwrightTargetElement?.contains(this.targetElement)) {
33009
- return this.generate(playwrightTargetElement, [], {
33058
+ return (await this.generate(playwrightTargetElement, [], {
33010
33059
  isPartOfListItem: false,
33011
33060
  isForContextElement: false,
33012
33061
  useTextContent,
33013
33062
  addCSSSelectorGenerator,
33014
33063
  expandCSSKeyElements
33015
- });
33064
+ })).map(
33065
+ ({ selector, locator }) => new LocatorChain([{ selector, locator }])
33066
+ );
33016
33067
  }
33017
33068
  } finally {
33018
33069
  return [];
@@ -33221,23 +33272,27 @@
33221
33272
  async reduceMultiCandidates(candidate) {
33222
33273
  await awaitSleep(100);
33223
33274
  const { elements } = candidate;
33224
- const parts = candidate.selector.split(" >> ");
33275
+ const parts = candidate.getSelector().split(" >> ");
33225
33276
  const newCandidates = [];
33226
33277
  const locatorBase = this.getLocatorBase(this.targetElement);
33227
33278
  const addCandidateWithSelector = /* @__PURE__ */ __name((selector) => {
33228
33279
  try {
33229
- newCandidates.push({
33230
- selector,
33231
- locator: this.getSelectorLocator(selector),
33232
- options: candidate.options
33233
- });
33280
+ newCandidates.push(
33281
+ new LocatorChain([
33282
+ {
33283
+ selector,
33284
+ locator: this.getSelectorLocator(selector)
33285
+ // options: candidate.options,
33286
+ }
33287
+ ])
33288
+ );
33234
33289
  return newCandidates.length >= this.MAX_MULTICANDIDATE_PROCESSING;
33235
33290
  } catch (error) {
33236
33291
  console.error(error);
33237
33292
  return false;
33238
33293
  }
33239
33294
  }, "addCandidateWithSelector");
33240
- const addCSSFilterToLocator = /* @__PURE__ */ __name((filter) => candidate.selector + // if we used css selector with tag name - concatentate it with the filter
33295
+ const addCSSFilterToLocator = /* @__PURE__ */ __name((filter) => candidate.getSelector() + // if we used css selector with tag name - concatentate it with the filter
33241
33296
  (parts[parts.length - 1] === this.targetElement.tagName.toLowerCase() ? filter : (
33242
33297
  // else - add as a new part
33243
33298
  ` >> internal:and=${escapeForAndLocator(
@@ -33245,7 +33300,7 @@
33245
33300
  )}`
33246
33301
  )), "addCSSFilterToLocator");
33247
33302
  const addIfSingularAndCheckLimit = /* @__PURE__ */ __name((selector) => {
33248
- if (locatorBase.locator(selector, candidate.options).elements.length === 1) {
33303
+ if (locatorBase.locator(selector).elements.length === 1) {
33249
33304
  return addCandidateWithSelector(selector);
33250
33305
  }
33251
33306
  return false;
@@ -33253,7 +33308,7 @@
33253
33308
  if (elements.length < 5) {
33254
33309
  const index2 = elements.indexOf(this.targetElement);
33255
33310
  if (index2 !== -1) {
33256
- addCandidateWithSelector(candidate.selector + ` >> nth=${index2}`);
33311
+ addCandidateWithSelector(candidate.getSelector() + ` >> nth=${index2}`);
33257
33312
  }
33258
33313
  }
33259
33314
  const parent = this.targetElement.parentElement;
@@ -33298,12 +33353,11 @@
33298
33353
  }
33299
33354
  getAllLocators(element, cssKeyElements, options = {}) {
33300
33355
  options = {
33301
- returnLocator: false,
33302
33356
  exact: true,
33303
33357
  useTextContent: true,
33304
33358
  ...options
33305
33359
  };
33306
- return [
33360
+ const locators = [
33307
33361
  this.getByRoleLocator(element, options),
33308
33362
  options.useTextContent ? this.getByTextLocator(element, options) : null,
33309
33363
  this.getByLabelLocator(element, options),
@@ -33311,26 +33365,80 @@
33311
33365
  this.getByTitleLocator(element, options),
33312
33366
  this.getByAltLocator(element, options),
33313
33367
  this.getTestIdLocator(element, options),
33314
- ...element === this.targetElement ? [this.getInnerFeaturesLocator(element, options)] : [],
33315
- ...cssKeyElements.filter((el) => el.element === element).map((el) => el.selector)
33368
+ ...element === this.targetElement ? [this.getInnerFeaturesLocator(element)] : [],
33369
+ ...cssKeyElements.filter((el) => el.element === element).map((el) => ({
33370
+ selector: el.selector,
33371
+ locator: this.getSelectorLocator(el.selector)
33372
+ }))
33316
33373
  ].filter((locator) => locator);
33374
+ if (!locators.length && options.bruteForceMode) {
33375
+ return locators.concat(this.findUniqueCSSLocatorForElement(element));
33376
+ }
33377
+ return locators;
33378
+ }
33379
+ /**
33380
+ * Used when in brute force mode to find unique CSS locator for the element
33381
+ */
33382
+ findUniqueCSSLocatorForElement(element, maxCandidates = 5) {
33383
+ const candidates = [];
33384
+ const addCandidate = /* @__PURE__ */ __name((selector) => {
33385
+ candidates.push([
33386
+ {
33387
+ selector,
33388
+ locator: `locator('${selector}')`
33389
+ }
33390
+ ]);
33391
+ return candidates.length >= maxCandidates;
33392
+ }, "addCandidate");
33393
+ for (const attribute of Array.from(element.attributes).filter(
33394
+ ({ name, value }) => value.length < 30 && !COVERED_ATTRIBUTES.includes(name)
33395
+ )) {
33396
+ try {
33397
+ const selector = `[${attribute.name}${attribute.value ? `="${attribute.value}"` : ""}]`;
33398
+ if (element.ownerDocument.querySelectorAll(selector).length === 1) {
33399
+ if (addCandidate(selector)) {
33400
+ return candidates;
33401
+ }
33402
+ }
33403
+ } catch (error) {
33404
+ }
33405
+ }
33406
+ for (const className of Array.from(element.classList).filter(
33407
+ (cls) => !CLASS_IGNORE_LIST.includes(cls)
33408
+ )) {
33409
+ try {
33410
+ const selector = `.${escapeSelector(className)}`;
33411
+ if (element.ownerDocument.querySelectorAll(selector).length === 1) {
33412
+ if (addCandidate(selector)) {
33413
+ return candidates;
33414
+ }
33415
+ }
33416
+ } catch (error) {
33417
+ }
33418
+ }
33419
+ return candidates;
33317
33420
  }
33318
- getInnerFeaturesLocator(element, options) {
33421
+ getInnerFeaturesLocator(element) {
33319
33422
  const tag = element.tagName.toLowerCase();
33320
33423
  if (!isInstanceOfHTMLElement(element) || ["html", "body", "div"].includes(tag)) {
33321
33424
  return;
33322
33425
  }
33323
- const innerFeatures = new InnerFeaturesExtractor().extract(element).filter((f2) => !f2.isRoot).slice(0, 4).map((feature) => getFeatureSelector(feature));
33426
+ const innerFeatures = new InnerFeaturesExtractor().extract(element).filter((f2) => !f2.isRoot).slice(0, 4).map((feature) => getFeatureSelector(feature).pop());
33324
33427
  if (innerFeatures.length < 2) {
33325
33428
  return;
33326
33429
  }
33327
- return options.returnLocator ? `locator('${tag}', {has: page.locator('${innerFeatures.join("")}')})` : tag + innerFeatures.map((feature) => `:has(${feature})`).join("");
33430
+ const selector = tag + innerFeatures.map((feature) => `:has(${feature})`).join("");
33431
+ return {
33432
+ selector,
33433
+ locator: `locator('${tag}', {has: page.locator('${selector}')})`
33434
+ };
33328
33435
  }
33329
33436
  getByRoleLocator(element, options = {}) {
33330
33437
  try {
33331
33438
  if (!isInstanceOfHTMLElement(element)) {
33332
33439
  throw new Error("Provided element is not an HTMLElement");
33333
33440
  }
33441
+ const usedFilters = {};
33334
33442
  const roleMappings = {
33335
33443
  button: "button",
33336
33444
  input: /* @__PURE__ */ __name((el) => el.type === "checkbox" ? "checkbox" : el.type === "radio" ? "radio" : "textbox", "input"),
@@ -33348,7 +33456,8 @@
33348
33456
  table: "table",
33349
33457
  tr: "row",
33350
33458
  th: "columnheader",
33351
- td: "cell"
33459
+ td: "cell",
33460
+ img: "img"
33352
33461
  };
33353
33462
  const tagName = element.tagName.toLowerCase();
33354
33463
  const role = element.getAttribute("role") || (roleMappings[tagName] instanceof Function ? roleMappings[tagName](element) : roleMappings[tagName]);
@@ -33375,22 +33484,35 @@
33375
33484
  if (attributeVal) {
33376
33485
  return normalize(attributeVal);
33377
33486
  }
33378
- const text = normalizeWhiteSpace(elementText(element).full);
33487
+ const injText = elementText(element);
33488
+ const text = normalizeWhiteSpace(injText.full);
33379
33489
  if (!text?.length) {
33380
33490
  return none;
33381
33491
  }
33382
33492
  const textVal = normalize(text);
33383
- if (this.usedFilters.hasText.includes(textVal.text)) {
33384
- return none;
33385
- }
33386
- this.usedFilters.hasText.push(textVal.text);
33493
+ usedFilters.hasText = textVal.text;
33387
33494
  return textVal;
33388
33495
  }, "getAccessibleName");
33389
33496
  const { text: name, exact } = getAccessibleName();
33390
33497
  if (name && name.length > 0 && name.length < 30) {
33391
33498
  props.push(["name", escapeForAttributeSelector(name, exact)]);
33392
33499
  }
33393
- return options.returnLocator ? `getByRole('${role}', { ${props.join(", ")} })` : `internal:role=${role}${props.map(([n2, v2]) => `[${n2}=${v2}]`).join("")}`;
33500
+ const getSelection = /* @__PURE__ */ __name(() => {
33501
+ return {
33502
+ locator: `getByRole('${role}', { ${props.join(", ")} })`,
33503
+ selector: `internal:role=${role}${props.map(([n2, v2]) => `[${n2}=${v2}]`).join("")}`,
33504
+ options: usedFilters
33505
+ };
33506
+ }, "getSelection");
33507
+ let selection = getSelection();
33508
+ if (name?.length && exact && !this.getLocatorBase(element).locator(selection.selector).elements.includes(element)) {
33509
+ props.find((p) => p[0] === "name")[1] = escapeForAttributeSelector(
33510
+ name,
33511
+ false
33512
+ );
33513
+ selection = getSelection();
33514
+ }
33515
+ return selection;
33394
33516
  } catch (error) {
33395
33517
  console.error("Error getting role locator", error.message);
33396
33518
  }
@@ -33404,20 +33526,36 @@
33404
33526
  if (!text?.length || text.length > 100) {
33405
33527
  return;
33406
33528
  }
33529
+ const usedFilters = {};
33407
33530
  if (Array.from(element.children).filter((el) => isInstanceOfHTMLElement(el)).some(
33408
33531
  (child) => normalizeWhiteSpace(elementText(child).full) === text
33409
33532
  )) {
33410
- if (!this.usedFilters.hasText.includes(text)) {
33411
- this.usedFilters.hasText.push(text);
33412
- return `${element.tagName.toLowerCase()} >> internal:has-text=${escapeForTextSelector(
33413
- text,
33414
- false
33415
- )}`;
33416
- }
33417
- return;
33533
+ usedFilters.hasText = text;
33534
+ const selector = `${element.tagName.toLowerCase()} >> internal:has-text=${escapeForTextSelector(
33535
+ text,
33536
+ false
33537
+ )}`;
33538
+ return {
33539
+ selector,
33540
+ locator: this.getSelectorLocator(selector),
33541
+ options: usedFilters
33542
+ };
33543
+ }
33544
+ const getSelection = /* @__PURE__ */ __name((exact) => {
33545
+ const escapedText = escapeForTextSelector(text, exact);
33546
+ const selector = "internal:text=" + escapedText;
33547
+ return {
33548
+ selector,
33549
+ locator: this.getSelectorLocator(selector),
33550
+ options: usedFilters
33551
+ };
33552
+ }, "getSelection");
33553
+ let selection = getSelection(options.exact);
33554
+ if (options.exact && !this.getLocatorBase(element).locator(selection.selector).elements.includes(element)) {
33555
+ selection = getSelection(false);
33556
+ 4;
33418
33557
  }
33419
- const escapedText = escapeForTextSelector(text, options.exact);
33420
- return options.returnLocator ? `getByText('${escapedText}')` : "internal:text=" + escapedText;
33558
+ return selection;
33421
33559
  } catch (error) {
33422
33560
  }
33423
33561
  }
@@ -33430,7 +33568,13 @@
33430
33568
  return;
33431
33569
  }
33432
33570
  const respond = /* @__PURE__ */ __name((label) => {
33433
- return options.returnLocator ? `getByLabel('${label}')` : `internal:label=${escapeForTextSelector(label, options.exact)}`;
33571
+ return {
33572
+ selector: `internal:label=${escapeForTextSelector(
33573
+ label,
33574
+ options.exact
33575
+ )}`,
33576
+ locator: `getByLabel('${label}')`
33577
+ };
33434
33578
  }, "respond");
33435
33579
  const ariaLabel = element.getAttribute("aria-label");
33436
33580
  if (ariaLabel) {
@@ -33485,10 +33629,13 @@
33485
33629
  }
33486
33630
  const attributeVal = element.getAttribute(attribute);
33487
33631
  if (attributeVal) {
33488
- return options.returnLocator ? `getBy${attribute.charAt(0).toUpperCase() + attribute.slice(1)}('${attributeVal}')` : `internal:attr=[${attribute}=${escapeForAttributeSelector(
33489
- attributeVal,
33490
- options.exact
33491
- )}]`;
33632
+ return {
33633
+ selector: `internal:attr=[${attribute}=${escapeForAttributeSelector(
33634
+ attributeVal,
33635
+ options.exact
33636
+ )}]`,
33637
+ locator: `getBy${attribute.charAt(0).toUpperCase() + attribute.slice(1)}('${attributeVal}')`
33638
+ };
33492
33639
  }
33493
33640
  } catch (error) {
33494
33641
  }
@@ -34050,7 +34197,10 @@
34050
34197
  static {
34051
34198
  __name(this, "ParentChainReducer");
34052
34199
  }
34053
- async reduce(element, options = { useLionTail: true }) {
34200
+ async reduce(element, options = {
34201
+ useLionTail: true,
34202
+ useChecksumIds: true
34203
+ }) {
34054
34204
  console.log("[ParentChainReducer] will reduce", element);
34055
34205
  if (!isNodeInstanceOf(element, "HTMLElement") && !isNodeInstanceOf(element, "SVGElement")) {
34056
34206
  throw new Error("Element is not an HTMLElement");
@@ -34073,7 +34223,7 @@
34073
34223
  * @param useLionTail whether to use LionTail to get the full dom tree close to the edge
34074
34224
  * @returns string
34075
34225
  */
34076
- getAncestorsHTMLSnippet(element, { useLionTail = true } = {}) {
34226
+ getAncestorsHTMLSnippet(element, { useLionTail = true, useChecksumIds = true } = {}) {
34077
34227
  this.elementsInSimplifiedDomByChecksumId = {};
34078
34228
  let lionTailMaxOuterHTMLLength = this.MAX_LIONTAIL_SIZE;
34079
34229
  let attributeMaxSize = this.MAX_ATTRIBUTE_SIZE;
@@ -34104,11 +34254,15 @@
34104
34254
  do {
34105
34255
  rootClone = ancestors[0].cloneNode(false);
34106
34256
  let currentClone = rootClone;
34257
+ const rootCloneSerialized = serializeElement(rootClone);
34107
34258
  for (let i2 = 1; i2 < ancestors.length; i2++) {
34108
34259
  if (useLionTail) {
34109
34260
  const lionTailSize = serializeElement(ancestors[i2]).length;
34110
- if (lionTailSize <= lionTailMaxOuterHTMLLength && serializeElement(rootClone).length + lionTailSize <= this.MAX_SNIPPET_SIZE) {
34111
- let newClone2 = this.deepCloneNode(ancestors[i2], counter);
34261
+ if (lionTailSize <= lionTailMaxOuterHTMLLength && rootCloneSerialized.length + lionTailSize <= this.MAX_SNIPPET_SIZE) {
34262
+ let newClone2 = this.deepCloneNode(
34263
+ ancestors[i2],
34264
+ useChecksumIds ? counter : void 0
34265
+ );
34112
34266
  currentClone.appendChild(newClone2);
34113
34267
  break;
34114
34268
  }
@@ -34116,9 +34270,11 @@
34116
34270
  let newClone = normalizeNode(
34117
34271
  ancestors[i2].cloneNode(ancestors[i2] === element)
34118
34272
  );
34119
- const id = counter.getAndIncrement();
34120
- newClone.setAttribute("checksumid", id);
34121
- this.elementsInSimplifiedDomByChecksumId[id] = ancestors[i2];
34273
+ if (useChecksumIds) {
34274
+ const id = counter.getAndIncrement();
34275
+ newClone.setAttribute("checksumid", id);
34276
+ this.elementsInSimplifiedDomByChecksumId[id] = ancestors[i2];
34277
+ }
34122
34278
  currentClone.appendChild(newClone);
34123
34279
  currentClone = newClone;
34124
34280
  }
@@ -34130,7 +34286,9 @@
34130
34286
  } while (rootClone.outerHTML.length > this.MAX_SNIPPET_SIZE && attributeMaxSize > 0);
34131
34287
  rootClone.querySelectorAll("*").forEach((el) => {
34132
34288
  });
34133
- let htmlSnippet = serializeElement(rootClone);
34289
+ let htmlSnippet = serializeElement(rootClone, {
34290
+ limitAttributeLength: true
34291
+ });
34134
34292
  return htmlSnippet;
34135
34293
  } catch (e2) {
34136
34294
  console.error("Error while getting ancestors HTML snippet", e2);
@@ -34153,7 +34311,7 @@
34153
34311
  const child = currentNode.childNodes[i2];
34154
34312
  const childClone = child.cloneNode(false);
34155
34313
  currentClone.appendChild(childClone);
34156
- if (isNodeInstanceOf(child, "HTMLElement")) {
34314
+ if (counter && isNodeInstanceOf(child, "HTMLElement")) {
34157
34315
  const id = counter.getAndIncrement();
34158
34316
  childClone.setAttribute("checksumid", id);
34159
34317
  this.elementsInSimplifiedDomByChecksumId[id] = child;
@@ -34417,7 +34575,6 @@
34417
34575
  },
34418
34576
  "*"
34419
34577
  );
34420
- console.log("selected", this.selected);
34421
34578
  }, "onClick");
34422
34579
  this.handleSubDocument = /* @__PURE__ */ __name((newRootDocument, defaultView) => {
34423
34580
  if (this.subDocumentInspector) {
@@ -34716,14 +34873,14 @@ ${data.locator}`
34716
34873
  * Tests a selector for a given element.
34717
34874
  */
34718
34875
  testElementSelector(selector, element, testVariables, testUniqueness = true) {
34719
- if (typeof element === "string") {
34720
- element = this.htmlReducer.getElementForChecksumId(
34721
- element
34722
- );
34876
+ const testElement = typeof element === "string" ? this.htmlReducer.getElementForChecksumId(element) : element;
34877
+ if (!testElement) {
34878
+ console.error("Element not found", element);
34879
+ return { valid: false, error: "Element not found" };
34723
34880
  }
34724
34881
  return new PlaywrightCustomLocatorGenerator().testSelector(
34725
34882
  selector,
34726
- element,
34883
+ testElement,
34727
34884
  testVariables,
34728
34885
  testUniqueness
34729
34886
  );
@@ -34735,16 +34892,18 @@ ${data.locator}`
34735
34892
  * @param useLionTail whether to use the lion tail for the parent chain.
34736
34893
  * Lion tail means that we include the closest HTML surrounding of the element
34737
34894
  * to add more context to the parent chain and the target element.
34895
+ * @param useChecksumIds whether to use checksumIds in the reduced HTML
34738
34896
  * @returns
34739
34897
  */
34740
- async getParentChainHTML(element, useLionTail = true) {
34898
+ async getParentChainHTML(element, useLionTail = true, useChecksumIds = true) {
34741
34899
  if (typeof element === "string") {
34742
34900
  element = this.htmlReducer.getElementForChecksumId(
34743
34901
  element
34744
34902
  );
34745
34903
  }
34746
34904
  const response = await this.parentChainReducer.reduce(element, {
34747
- useLionTail
34905
+ useLionTail,
34906
+ useChecksumIds
34748
34907
  });
34749
34908
  return response.reducedHTML;
34750
34909
  }