@checksum-ai/runtime 1.1.18 → 1.1.23

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.
@@ -3,3 +3,4 @@ node_modules/
3
3
  /playwright-report/
4
4
  /blob-report/
5
5
  /playwright/.cache/
6
+ .auth
@@ -1,65 +1,3 @@
1
- # Checksum.ai Tests
1
+ ### Checksum.ai Runtime
2
2
 
3
- ## Quick Start
4
-
5
- 1. Run `npm install -D checksumai`.
6
- 2. Navigate to the directory where you want to add Checksum tests and run `npx checksumai init`.
7
- 3. Run `npx playwright install --with-deps` to install Playwright dependencies.
8
- 4. Edit `checksum.config.ts` to include necessary configurations such as:
9
- - `apiKey`
10
- - `baseURL`
11
- - `username`
12
- - `password`
13
- 5. Update `login.ts` with your login function using Playwright. See the Login Function section below for guidance.
14
- 6. Run `npx checksumai test` to execute the example test and verify successful login.
15
- 7. If you haven't already, visit [app.checksum.ai](https://app.checksum.ai) to complete the configuration and generate a test. Then, wait for the pull request (PR) to be created and approve it.
16
-
17
- ## Login Function
18
-
19
- 1. This function is executed at the start of each test.
20
- 2. We recommend using a consistent seeded user for every test. For example, before each test, call a webhook that creates a user, seeds it with data, and returns the username and password. This approach ensures test reliability and allows parallel test execution. Configure this webhook [in your project](https://app.checksum.ai/#/settings/wizard) for consistent test generation.
21
- 3. After logging in, assert that the login was successful. Playwright waits for assertions to be correct, ensuring that the page is ready for interaction before proceeding.
22
- 4. To reuse authentication states between tests, refer to the Playwright guide on [authentication](https://playwright.dev/docs/auth). At the start of the login function, check if the user is already authenticated and return if so.
23
-
24
- ## Checksum AI Magic
25
-
26
- The tests generated by Checksum are based on Playwright. When executed using the Checksum CLI with an API key, Checksum enhances Playwright's functionality, improving test reliability and automatically maintaining tests.
27
-
28
- ### Autonomous Test Agent
29
-
30
- Checksum runs your Playwright tests regularly, but we added a few extra features to make tests more reliable. All of the features can be turned on/off through `checksum.config.ts`
31
-
32
- **Smart Selectors**
33
- When generating tests, Checksum stores extensive metadata for each action (see the `test-data` folder). If a traditional selector fails, this metadata is used for correction. For example, if a test identifies an element by its ID but the ID changes, Checksum utilizes other data points (e.g., element class, text, parents) to locate the element. Use the `checksumSelector("<id>")` method to link an action to its metadata. Do not alter the IDs.
34
-
35
- **Checksum AI**
36
- If Smart Selectors also fail, Checksum's custom-trained model can regenerate the failed section of the test. In such cases, the model might add, remove, or alter actions to achieve the same objectives, without changing the assertions. The assumption is that as long as the assertions pass, the model has successfully fixed the test. Use the `.checksumAI("<natural language description of the test>")` method to guide the model in fixing the test.
37
-
38
- You can modify the description as needed for our model. Additionally, you can include steps with only ChecksumAI descriptions, prompting our model to generate the Playwright code. For example, `await page.checksumAI("Click on 'New Task' button")` without the actual action will have our model generate the necessary Playwright code. You can even author entire tests in this manner.
39
-
40
- ### Run Modes
41
-
42
- Checksum offers three run modes:
43
-
44
- 1. **Normal** - Tests are executed using the Autonomous Test Agent as defined in the config file.
45
- 2. **Heal** - If the Autonomous Test Agent corrects a test, a new test file with the fix is created. By default, this file is created locally, but you can also configure the Agent to open a PR to your GitHub repository by setting `autoHealPRs` to true.
46
- 3. **Refactor (Work in Progress)** - The Autonomous Test Agent runs the test and, for each action, regenerates a regular Playwright selector, a Smart Selector, and a Checksum AI description.
47
-
48
- ### Mock Data
49
-
50
- When generating tests, Checksum records all backend responses, allowing tests to run in the same backend context. This is particularly useful for debugging or initial test runs, especially if your testing database/context differs from that used for test generation. Note that if your backend response format changes, the mocked data may not work as expected.
51
-
52
- ### CLI Commands
53
-
54
- 1. `init` - Initialize the Checksum directory and configurations.
55
- 2. `test` - Run Checksum tests. Accepts all [Playwright command line flags](https://playwright.dev/docs/test-cli). To override `checksum.config.ts`, pass full or partial JSON as a string, e.g., `--checksum-config='{"baseURL": "https://example.com"}'`.
56
-
57
- ## Running with GitHub Actions
58
-
59
- See the example file `github-actions.example.yml`.
60
-
61
- ## Troubleshooting
62
-
63
- **Q: I'm seeing various exceptions when I run "npx checksumai test", even before the test starts.**
64
-
65
- A: If you had a pre-installed version of Playwright, it might not be compatible with Checksum. Uninstall the Playwright and Checksum libraries, delete the relevant folder from `node_modules`, and run `npm install -D checksumai`.
3
+ To learn more about how to run tests using the Checksum.ai Runtime please refer to the [Github repository's readme](https://github.com/checksum-ai/checksum-ai-runtime?tab=readme-ov-file#checksumai-runtime)
@@ -1,4 +1,6 @@
1
1
  import { RunMode, getChecksumConfig } from "@checksum-ai/runtime";
2
+ import { resolve } from "path";
3
+ require("dotenv").config({ path: resolve(__dirname, ".env") });
2
4
 
3
5
  export default getChecksumConfig({
4
6
  /**
package/checksumlib.js CHANGED
@@ -22123,10 +22123,16 @@
22123
22123
  this.start = /* @__PURE__ */ __name(() => {
22124
22124
  this.stopRRWebRecording = record({
22125
22125
  emit: this.eventHandler,
22126
+ // [rrweb config changes]
22126
22127
  sampling: {
22127
22128
  mousemove: false
22128
22129
  },
22129
- userTriggeredOnInput: true
22130
+ //mousemoveWait: 100,
22131
+ userTriggeredOnInput: true,
22132
+ maskInputOptions: {
22133
+ password: false
22134
+ // Do not mask password inputs
22135
+ }
22130
22136
  });
22131
22137
  if (this.logNativeMutationObserver) {
22132
22138
  this.selfObserve();
@@ -22733,32 +22739,40 @@
22733
22739
  this.rrwebEventsLoaded = false;
22734
22740
  this.loadRRwebEvents = [];
22735
22741
  this.rrwebCrossFrameEventIdCounter = 0;
22742
+ this.firstRequestedIndex = void 0;
22736
22743
  }
22737
22744
  static {
22738
22745
  __name(this, "RrwebEventsStorageManager");
22739
22746
  }
22740
22747
  async initRRwebEvents() {
22741
- if (this.initialized) {
22742
- console.warn(
22743
- "[RrwebEventsStorageManager] initRRwebEvents called more than once"
22744
- );
22745
- return;
22746
- }
22747
- this.initialized = true;
22748
- this.rrwebEventsIndexedDB = new IndexedDBClient("checksum", "rrwebEvents");
22749
22748
  try {
22750
- await this.rrwebEventsIndexedDB.open();
22749
+ if (this.initialized) {
22750
+ console.warn(
22751
+ "[RrwebEventsStorageManager] initRRwebEvents called more than once"
22752
+ );
22753
+ return;
22754
+ }
22755
+ this.initialized = true;
22756
+ this.rrwebEventsIndexedDB = new IndexedDBClient(
22757
+ "checksum",
22758
+ "rrwebEvents"
22759
+ );
22760
+ try {
22761
+ await this.rrwebEventsIndexedDB.open();
22762
+ } catch (e2) {
22763
+ return;
22764
+ }
22765
+ this.rrwebCrossFrameEventIdCounter = await this.rrwebEventsIndexedDB.count();
22766
+ this.loadRRwebEvents.forEach((event) => {
22767
+ this.rrwebEventsIndexedDB.set(
22768
+ event,
22769
+ this.rrwebCrossFrameEventIdCounter++
22770
+ );
22771
+ });
22772
+ this.rrwebEventsLoaded = true;
22751
22773
  } catch (e2) {
22752
- return;
22774
+ console.log(e2);
22753
22775
  }
22754
- this.rrwebCrossFrameEventIdCounter = await this.rrwebEventsIndexedDB.count();
22755
- this.loadRRwebEvents.forEach((event) => {
22756
- this.rrwebEventsIndexedDB.set(
22757
- event,
22758
- this.rrwebCrossFrameEventIdCounter++
22759
- );
22760
- });
22761
- this.rrwebEventsLoaded = true;
22762
22776
  }
22763
22777
  onRRwebEvent(event) {
22764
22778
  if (this.rrwebEventsLoaded) {
@@ -22792,14 +22806,24 @@
22792
22806
  body: JSON.stringify(rrwebEvents)
22793
22807
  });
22794
22808
  }
22795
- async getRRwebEvents(lowerBoundKey) {
22796
- if (!this.rrwebEventsLoaded) {
22809
+ async getRRwebEvents(lowerBoundKey, size) {
22810
+ try {
22811
+ if (this.firstRequestedIndex === void 0) {
22812
+ this.firstRequestedIndex = lowerBoundKey;
22813
+ }
22814
+ lowerBoundKey -= this.firstRequestedIndex;
22815
+ if (!this.rrwebEventsLoaded) {
22816
+ return [];
22817
+ }
22818
+ const bound = size ? IDBKeyRange.bound(lowerBoundKey, lowerBoundKey + size) : IDBKeyRange.lowerBound(lowerBoundKey);
22819
+ const events = await this.rrwebEventsIndexedDB.getAll(
22820
+ bound
22821
+ );
22822
+ return events;
22823
+ } catch (e2) {
22824
+ console.log(e2);
22797
22825
  return [];
22798
22826
  }
22799
- const events = await this.rrwebEventsIndexedDB.getAll(
22800
- IDBKeyRange.lowerBound(lowerBoundKey)
22801
- );
22802
- return events;
22803
22827
  }
22804
22828
  async getRRwebEvent(key) {
22805
22829
  if (!this.rrwebEventsLoaded) {
@@ -22885,6 +22909,14 @@
22885
22909
  return str.substring(0, firstParantheses + 1) + "`" + str.substring(firstParantheses + 2, str.length - 2) + "`)";
22886
22910
  }
22887
22911
  __name(replaceLocatorQuotesWithTicks, "replaceLocatorQuotesWithTicks");
22912
+ function getElementWindowPlaywright(node) {
22913
+ const elementWindow = node.ownerDocument.defaultView;
22914
+ if (elementWindow !== window && !elementWindow.playwright) {
22915
+ elementWindow.playwright = window.playwright;
22916
+ }
22917
+ return elementWindow.playwright;
22918
+ }
22919
+ __name(getElementWindowPlaywright, "getElementWindowPlaywright");
22888
22920
  var COVERED_ATTRIBUTES = [
22889
22921
  "role",
22890
22922
  "aria-label",
@@ -22903,7 +22935,8 @@
22903
22935
  "style",
22904
22936
  "href",
22905
22937
  "src",
22906
- "path"
22938
+ "path",
22939
+ "type"
22907
22940
  ];
22908
22941
 
22909
22942
  // ../../node_modules/css-selector-generator/esm/utilities-iselement.js
@@ -23592,6 +23625,7 @@
23592
23625
  }, "guardReturn");
23593
23626
 
23594
23627
  // src/lib/test-generator/selectors/pw-custom-locator-generator.ts
23628
+ var DEBUG_MODE = false;
23595
23629
  var PlaywrightCustomLocatorGenerator = class _PlaywrightCustomLocatorGenerator {
23596
23630
  constructor(rootNode = document) {
23597
23631
  this.rootNode = rootNode;
@@ -23871,7 +23905,9 @@
23871
23905
  });
23872
23906
  element = element.parentElement;
23873
23907
  }
23874
- console.log("Added CSS key features", added);
23908
+ if (added.length) {
23909
+ console.log("Added CSS key features", added);
23910
+ }
23875
23911
  } catch (error) {
23876
23912
  console.error("Error extracting CSS key features", error);
23877
23913
  }
@@ -23995,13 +24031,6 @@
23995
24031
  console.error("[filterListItemsSelectors] error", error.message);
23996
24032
  }
23997
24033
  }
23998
- getElementWindowPlaywright(node) {
23999
- const elementWindow = node.ownerDocument.defaultView;
24000
- if (elementWindow !== window && !elementWindow.playwright) {
24001
- elementWindow.playwright = window.playwright;
24002
- }
24003
- return elementWindow.playwright;
24004
- }
24005
24034
  /**
24006
24035
  * Returns the relevant locator for testing the element
24007
24036
  * If we are generating a relative selector - returns playwright entry point
@@ -24010,7 +24039,7 @@
24010
24039
  * @returns Locator or playwright entry point
24011
24040
  */
24012
24041
  getLocatorBase(element) {
24013
- const playwright = this.getElementWindowPlaywright(element);
24042
+ const playwright = getElementWindowPlaywright(element);
24014
24043
  if (this.rootNode === document) {
24015
24044
  return playwright;
24016
24045
  }
@@ -24020,9 +24049,10 @@
24020
24049
  * Returns locator representation of a selector
24021
24050
  */
24022
24051
  getSelectorLocator(selector, element) {
24023
- return this.getElementWindowPlaywright(
24024
- element ?? this.targetElement
24025
- ).asLocator("javascript", selector);
24052
+ return getElementWindowPlaywright(element ?? this.targetElement).asLocator(
24053
+ "javascript",
24054
+ selector
24055
+ );
24026
24056
  }
24027
24057
  /**
24028
24058
  * This method tries to reduce the number of elements returned by the selectors that return multiple elements
@@ -24175,16 +24205,31 @@
24175
24205
  )
24176
24206
  ]);
24177
24207
  }
24178
- let val = element.getAttribute("aria-label") ?? element.getAttribute("aria-labelledby") ?? (options.useTextContent ? normalizeWhiteSpace(elementText(element).full) : "");
24179
- const { text: name, exact } = this.limitTextLength(
24180
- this.normalize(val),
24181
- options.exact
24182
- );
24208
+ const getAccessibleName = /* @__PURE__ */ __name(() => {
24209
+ const normalize = /* @__PURE__ */ __name((txt) => this.limitTextLength(this.normalize(txt), options.exact), "normalize");
24210
+ const none = { text: "", exact: false };
24211
+ const attributeVal = element.getAttribute("aria-label") ?? element.getAttribute("aria-labelledby") ?? element.getAttribute("title");
24212
+ if (attributeVal) {
24213
+ return normalize(attributeVal);
24214
+ }
24215
+ const text = normalizeWhiteSpace(elementText(element).full);
24216
+ if (!text?.length) {
24217
+ return none;
24218
+ }
24219
+ const textVal = normalize(text);
24220
+ if (this.usedFilters.hasText.includes(textVal.text)) {
24221
+ return none;
24222
+ }
24223
+ this.usedFilters.hasText.push(textVal.text);
24224
+ return textVal;
24225
+ }, "getAccessibleName");
24226
+ const { text: name, exact } = getAccessibleName();
24183
24227
  if (name && name.length > 0 && name.length < 30) {
24184
24228
  props.push(["name", escapeForAttributeSelector(name, exact)]);
24185
24229
  }
24186
24230
  return options.returnLocator ? `getByRole('${role}', { ${props.join(", ")} })` : `internal:role=${role}${props.map(([n2, v2]) => `[${n2}=${v2}]`).join("")}`;
24187
24231
  } catch (error) {
24232
+ console.error("Error getting role locator", error);
24188
24233
  }
24189
24234
  }
24190
24235
  getByTextLocator(element, options = {}) {
@@ -24197,10 +24242,7 @@
24197
24242
  return;
24198
24243
  }
24199
24244
  if (Array.from(element.children).filter((el) => el instanceof HTMLElement).some(
24200
- // (child) => (child as HTMLElement).innerText === textContent
24201
24245
  (child) => normalizeWhiteSpace(elementText(child).full) === text
24202
- // this.normalize((child as HTMLElement).textContent) === text
24203
- // this.normalize((child as HTMLElement).textContent) === text
24204
24246
  )) {
24205
24247
  if (!this.usedFilters.hasText.includes(text)) {
24206
24248
  this.usedFilters.hasText.push(text);
@@ -24305,7 +24347,9 @@
24305
24347
  return { text, exact };
24306
24348
  }
24307
24349
  checkTimeout(throwError = true) {
24308
- return false;
24350
+ if (DEBUG_MODE) {
24351
+ return false;
24352
+ }
24309
24353
  if (this.MAX_PROCESSING_TIME && Date.now() - this.startTimestamp > this.MAX_PROCESSING_TIME) {
24310
24354
  if (throwError) {
24311
24355
  throw new TimeoutError();
@@ -24408,7 +24452,10 @@
24408
24452
  ).filter((s2) => s2 && s2.trim().length > 0),
24409
24453
  playwrightSelectors: await new PlaywrightCustomLocatorGenerator(
24410
24454
  commonRoot
24411
- ).generate(targetElement.element),
24455
+ ).generate(targetElement.element, [], {
24456
+ addCSSSelectorGenerator: false,
24457
+ expandCSSKeyElements: false
24458
+ }),
24412
24459
  featuresMetadata: targetElement.featuresMetadata
24413
24460
  },
24414
24461
  contextElements
@@ -24430,13 +24477,14 @@
24430
24477
  }
24431
24478
  });
24432
24479
  const commonParents = this.getSmallestCommonParents(elementGroups).map(this.calculateElementSize).sort((a2, b) => a2.size - b.size).map((e2) => e2.element);
24433
- for (const cp of commonParents) {
24434
- const locator = window.playwright.locator(window.playwright.selector(cp));
24435
- const { element } = locator.locator(targetSelector);
24436
- if (element) {
24437
- return window.playwright.selector(element);
24438
- }
24480
+ if (!targetSelector) {
24481
+ return commonParents.map((cp) => getElementWindowPlaywright(cp).selector(cp)).filter((s2) => s2);
24439
24482
  }
24483
+ return commonParents.map((cp) => {
24484
+ const playwright = getElementWindowPlaywright(cp);
24485
+ const { element } = playwright.locator(playwright.selector(cp)).locator(targetSelector);
24486
+ return element ? playwright.selector(element) : null;
24487
+ }).filter((e2) => e2);
24440
24488
  }
24441
24489
  /**
24442
24490
  * Performs a compound selection from metadata
@@ -24465,10 +24513,10 @@
24465
24513
  compoundSelector.targetSelection
24466
24514
  );
24467
24515
  if (histogram.size > 0) {
24468
- const { selector, locator } = await new PlaywrightElementSelectorGenerator().getSelectorAndLocator(
24469
- histogram.entries().next().value[0]
24516
+ const selectors = Array.from(histogram.keys()).slice(0, 20).map(
24517
+ (el) => getElementWindowPlaywright(el).generateSelectorAndLocator(el)
24470
24518
  );
24471
- return { selector, locator };
24519
+ return selectors;
24472
24520
  }
24473
24521
  }
24474
24522
  }
@@ -24492,7 +24540,7 @@
24492
24540
  targetSelection.cssSelectors.forEach((selector) => {
24493
24541
  try {
24494
24542
  addElementstoHistogram(
24495
- Array.from(commonParent.querySelectorAll(selector))
24543
+ querySelectorAllExtended(selector, commonParent)
24496
24544
  );
24497
24545
  } catch (error) {
24498
24546
  }
@@ -24638,7 +24686,7 @@
24638
24686
  (combination) => this.createContextualSelector(commonRoot, target, combination)
24639
24687
  ).filter((s2) => s2).filter((s2) => {
24640
24688
  try {
24641
- const result = commonRoot.querySelectorAll(s2);
24689
+ const result = querySelectorAllExtended(s2, commonRoot);
24642
24690
  return result.length === 1 && result[0] === target;
24643
24691
  } catch (error) {
24644
24692
  return false;
@@ -24771,7 +24819,7 @@
24771
24819
  getSmallestCommonParentsFromMetadata(contextElements) {
24772
24820
  const elementsBySelector = contextElements.map((ce) => {
24773
24821
  try {
24774
- const elements = Array.from(document.querySelectorAll(ce.selector));
24822
+ const elements = querySelectorAllExtended(ce.selector);
24775
24823
  return ce.selection.type === "content" ? elements.filter(
24776
24824
  (element) => element.textContent === ce.selection.value
24777
24825
  ) : elements;
@@ -24931,6 +24979,116 @@
24931
24979
  }
24932
24980
  };
24933
24981
 
24982
+ // src/lib/test-generator/files-observer.ts
24983
+ var FilesObserver = class {
24984
+ static {
24985
+ __name(this, "FilesObserver");
24986
+ }
24987
+ constructor(sessionMirror) {
24988
+ this.fileInputsWithListeners = /* @__PURE__ */ new Set();
24989
+ this.filesMap = {};
24990
+ this.observer = null;
24991
+ this.sessionMirror = sessionMirror;
24992
+ }
24993
+ // Get files for a specific input by rrwebId
24994
+ async getFilesByRrwebId(rrwebId) {
24995
+ const files = this.filesMap[rrwebId] ?? [];
24996
+ try {
24997
+ const filesAsBase64 = await Promise.all(
24998
+ files.map(async (file) => ({
24999
+ data: await convertFileToBase64(file),
25000
+ fileName: file.name
25001
+ }))
25002
+ );
25003
+ this.filesMap[rrwebId] = [];
25004
+ return filesAsBase64;
25005
+ } catch {
25006
+ console.log("Error while parsing files to base 64");
25007
+ }
25008
+ }
25009
+ // Initialize the observer to track changes in the window
25010
+ init() {
25011
+ console.log("Starting observation...");
25012
+ console.log(this.sessionMirror);
25013
+ this.observeDOMChanges();
25014
+ window.addEventListener("unload", this.cleanup.bind(this));
25015
+ }
25016
+ // Handle the file input change event
25017
+ handleFileChange(event) {
25018
+ console.log("handleFileChange", this.sessionMirror);
25019
+ const target = event.target;
25020
+ const newFiles = Array.from(target.files || []);
25021
+ const rrwebMetaData = this.sessionMirror.getMeta(target);
25022
+ this.filesMap[rrwebMetaData.id] = newFiles;
25023
+ console.log(`Files updated for ID: ${rrwebMetaData.id}`, newFiles);
25024
+ }
25025
+ // Add listeners to file input elements
25026
+ addFileInputListeners(fileInputs) {
25027
+ fileInputs.forEach((input) => {
25028
+ if (!this.fileInputsWithListeners.has(input)) {
25029
+ input.addEventListener("change", this.handleFileChange.bind(this));
25030
+ this.fileInputsWithListeners.add(input);
25031
+ }
25032
+ });
25033
+ }
25034
+ // Observe DOM changes in the window's document
25035
+ observeDOMChanges() {
25036
+ const document2 = window.document;
25037
+ const initialFileInputs = document2.querySelectorAll(
25038
+ 'input[type="file"]'
25039
+ );
25040
+ this.addFileInputListeners(initialFileInputs);
25041
+ const onBodyReady = /* @__PURE__ */ __name(() => {
25042
+ this.observer = new MutationObserver((mutationsList) => {
25043
+ mutationsList.forEach((mutation) => {
25044
+ if (mutation.type === "childList") {
25045
+ const fileInputs = mutation.target.querySelectorAll(
25046
+ 'input[type="file"]'
25047
+ );
25048
+ this.addFileInputListeners(fileInputs);
25049
+ }
25050
+ });
25051
+ });
25052
+ this.observer.observe(document2.body, { childList: true, subtree: true });
25053
+ }, "onBodyReady");
25054
+ const bodyObserver = new MutationObserver(function() {
25055
+ if (document2.body) {
25056
+ bodyObserver.disconnect();
25057
+ onBodyReady();
25058
+ }
25059
+ });
25060
+ bodyObserver.observe(document2.documentElement, { childList: true });
25061
+ }
25062
+ // Cleanup the observer and remove event listeners
25063
+ cleanup() {
25064
+ console.log("Cleaning up files observer...");
25065
+ if (this.observer) {
25066
+ this.observer.disconnect();
25067
+ }
25068
+ this.fileInputsWithListeners.forEach((input) => {
25069
+ input.removeEventListener("change", this.handleFileChange);
25070
+ });
25071
+ this.fileInputsWithListeners.clear();
25072
+ this.filesMap = {};
25073
+ }
25074
+ };
25075
+ var convertFileToBase64 = /* @__PURE__ */ __name((file) => {
25076
+ return new Promise((resolve, reject) => {
25077
+ const reader = new FileReader();
25078
+ reader.onloadend = () => {
25079
+ if (reader.result) {
25080
+ resolve(reader.result.toString());
25081
+ } else {
25082
+ reject(new Error("File reading failed"));
25083
+ }
25084
+ };
25085
+ reader.onerror = () => {
25086
+ reject(new Error("File reading failed"));
25087
+ };
25088
+ reader.readAsDataURL(file);
25089
+ });
25090
+ }, "convertFileToBase64");
25091
+
24934
25092
  // src/lib/test-generator/test-generator.ts
24935
25093
  var LOGS_PREFIX = "$checksum";
24936
25094
  var ChecksumTestGenerator = class {
@@ -25042,7 +25200,8 @@
25042
25200
  }
25043
25201
  // -------- [API] -------- //
25044
25202
  init(appSpecificRules, config = {}, {
25045
- sessionRecorder: initSessionRecorder = true
25203
+ sessionRecorder: initSessionRecorder = true,
25204
+ filesObserver: initFilesObserver = false
25046
25205
  } = {}, options = {}) {
25047
25206
  this.appSpecificRules = appSpecificRules;
25048
25207
  this.config = config;
@@ -25054,10 +25213,20 @@
25054
25213
  }
25055
25214
  if (initSessionRecorder) {
25056
25215
  this.sessionMirror = new SessionRecorder((event) => {
25216
+ console.log("send events");
25217
+ window.checksumSendMessage?.("vtg", { type: "event", data: event });
25218
+ try {
25219
+ window["onRrwebEvents"]?.([event]);
25220
+ } catch (e2) {
25221
+ }
25057
25222
  rrwebEventsStorageManager.onRRwebEvent(event);
25058
25223
  });
25059
25224
  rrwebEventsStorageManager.initRRwebEvents();
25060
25225
  }
25226
+ if (initFilesObserver) {
25227
+ this.filesObserver = new FilesObserver(this.sessionMirror);
25228
+ this.filesObserver.init();
25229
+ }
25061
25230
  if (this.sessionMirror) {
25062
25231
  this.sessionMirror.start();
25063
25232
  this.htmlReducer.setSessionRecorder(this.sessionMirror);
@@ -25880,8 +26049,13 @@
25880
26049
  super.setEventHandlers(eventHandlers);
25881
26050
  }
25882
26051
  async handleEvents(events, len) {
25883
- console.log("starting handleEvents", len);
25884
26052
  try {
26053
+ events = events.filter(
26054
+ (event) => event.timestamp >= (this.lastSeenTimestamp ? this.lastSeenTimestamp : 0)
26055
+ );
26056
+ if (events.length) {
26057
+ this.lastSeenTimestamp = events[events.length - 1].timestamp;
26058
+ }
25885
26059
  for (const event of events) {
25886
26060
  await this.eventProcessor.processEvent(event, this.shouldHandleEvents);
25887
26061
  await this.skipEvents([event]);
@@ -26114,6 +26288,7 @@
26114
26288
  constructor() {
26115
26289
  super();
26116
26290
  this.events = [];
26291
+ this.previousEvent = null;
26117
26292
  this.interactions = [];
26118
26293
  this.lastInteractionEventIndex = 0;
26119
26294
  this.sequences = [];
@@ -26162,7 +26337,9 @@
26162
26337
  if (!draggableNode) {
26163
26338
  return;
26164
26339
  }
26165
- const draggableSelector = await this.elementSelector.getSelector(draggableNode);
26340
+ const draggableSelector = await this.elementSelector.getSelector(
26341
+ draggableNode
26342
+ );
26166
26343
  if (!draggableSelector) {
26167
26344
  return;
26168
26345
  }
@@ -26172,7 +26349,9 @@
26172
26349
  if (!dropzoneNode) {
26173
26350
  return;
26174
26351
  }
26175
- const dropzoneSelector = await this.elementSelector.getSelector(dropzoneNode);
26352
+ const dropzoneSelector = await this.elementSelector.getSelector(
26353
+ dropzoneNode
26354
+ );
26176
26355
  if (!dropzoneSelector) {
26177
26356
  return;
26178
26357
  }
@@ -26223,6 +26402,7 @@
26223
26402
  this.events.push(event);
26224
26403
  }
26225
26404
  async postEvent(event, result) {
26405
+ this.previousEvent = event;
26226
26406
  try {
26227
26407
  await this.performSequenceSearch();
26228
26408
  } catch (e2) {
@@ -26252,7 +26432,9 @@
26252
26432
  event,
26253
26433
  selector
26254
26434
  );
26435
+ action.rrwebId = data.id;
26255
26436
  if (action.eventCode === "input" /* Input */) {
26437
+ action.timestamp = this.previousEvent?.timestamp ?? event.timestamp;
26256
26438
  action.fillValue = data.text;
26257
26439
  this.inputFilter = {
26258
26440
  action,
@@ -26264,7 +26446,7 @@
26264
26446
  }
26265
26447
  if (action.eventCode === "upload_files" /* UploadFiles */) {
26266
26448
  action.files = [];
26267
- action.files.push(data.text);
26449
+ action.files.push(data.text.split("\\").pop());
26268
26450
  }
26269
26451
  this.addInteraction(action);
26270
26452
  }
@@ -26397,6 +26579,9 @@
26397
26579
  }
26398
26580
  }
26399
26581
  addInteraction(action) {
26582
+ if (!action.id) {
26583
+ action.id = Date.now().toString();
26584
+ }
26400
26585
  this.interactions.push(action);
26401
26586
  this.lastInteractionEventIndex = this.events.length;
26402
26587
  }
@@ -26461,6 +26646,9 @@
26461
26646
  this.singleSelection = true;
26462
26647
  this.subDocumentInspector = null;
26463
26648
  this.listening = false;
26649
+ this.onMouseOut = /* @__PURE__ */ __name((event) => {
26650
+ elementHighlighter.clearHighlights();
26651
+ }, "onMouseOut");
26464
26652
  this.onMouseOver = /* @__PURE__ */ __name(async (event) => {
26465
26653
  const target = event.composedPath()[0];
26466
26654
  if ("getRootNode" in target) {
@@ -26532,6 +26720,13 @@
26532
26720
  if (this.singleSelection) {
26533
26721
  this.topLevelInspector ? this.topLevelInspector.stop() : this.stop();
26534
26722
  }
26723
+ window.parent.postMessage(
26724
+ {
26725
+ type: "inspector-selection",
26726
+ data: this.selected.at(this.selected.length - 1)
26727
+ },
26728
+ "*"
26729
+ );
26535
26730
  console.log("selected", this.selected);
26536
26731
  }, "onClick");
26537
26732
  this.handleSubDocument = /* @__PURE__ */ __name((newRootDocument, defaultView) => {
@@ -26568,6 +26763,7 @@
26568
26763
  this.stop();
26569
26764
  this.cleanSelection();
26570
26765
  this.rootDocument.addEventListener("mouseover", this.onMouseOver);
26766
+ this.rootDocument.addEventListener("mouseout", this.onMouseOut);
26571
26767
  this.listening = true;
26572
26768
  }
26573
26769
  stop(clean = false) {
@@ -26617,14 +26813,18 @@
26617
26813
  static {
26618
26814
  __name(this, "VisualTestGenerator");
26619
26815
  }
26620
- init(shouldHandleEvents = true) {
26621
- this.timeMachine.setEventHandlers(this.eventHandlers, shouldHandleEvents);
26816
+ init() {
26817
+ this.timeMachine.setEventHandlers(this.eventHandlers, true);
26622
26818
  }
26623
26819
  consumeInteractions() {
26624
26820
  const interactions = this.eventHandlers.getInteractions(
26625
26821
  this.lengthOfConsumedInteractions
26626
26822
  );
26627
26823
  this.lengthOfConsumedInteractions += interactions.length;
26824
+ window.checksumSendMessage("vtg", {
26825
+ type: "consume-interactions",
26826
+ data: interactions
26827
+ });
26628
26828
  return interactions;
26629
26829
  }
26630
26830
  startInspector(singleSelection = true, rootDocument) {
@@ -26637,7 +26837,7 @@
26637
26837
  return document.querySelector(".replayer-wrapper > iframe").contentDocument;
26638
26838
  }
26639
26839
  stopInspector() {
26640
- this.elementInspector.stop();
26840
+ this.elementInspector?.stop();
26641
26841
  }
26642
26842
  consumeSelections() {
26643
26843
  return this.elementInspector.consumeSelections();