Skip to content

telerik/roadkill

Repository files navigation

Roadkill

A WebDriver for the masses!

Version: Alpha!

A node.js testing solution over the WebDriver protocol. Will also consider WebDriver BiDi.

Requirements

  • Node.js 22+ (required for ECMAScript 2024 Explicit Resource Management)
  • TypeScript 5.2+ (for native Disposable support)

Powered by:

A WebDriver based slim testing framework. Closes the gaps between QAs and Front-End developers by:

  • Sharing the same TypeScript or JavaScript language with Angular, React and Vue front-end developers
  • Stay within the nodejs ecosystem
  • Skill-transfer between QAs and Front-End devs
  • ECMAScript 2024 Only - Pure symbol-based disposables, no legacy methods
  • Node.js 22+ Required - Cutting-edge JavaScript runtime features
  • Zero Backward Compatibility - Clean slate approach

AI-Powered DOM Analysis

Roadkill includes advanced DOM snapshot capabilities designed for AI assistants like GitHub Copilot. The dom-snapshot and dom-test-selector tools provide comprehensive page structure analysis to help craft robust selectors:

  • dom-snapshot: Captures complete DOM structure with element positioning, visibility states, and semantic information
  • dom-test-selector: Tests CSS/XPath selectors and shows element hierarchy with matches, parents, and children
  • AI Integration: Optimized for Copilot to understand page layout and suggest stable, maintainable selectors
  • Intelligent Filtering: Automatically excludes non-essential elements (style, script tags) for cleaner analysis
  • Multiple Formats: Supports both HTML and JSON output formats for different AI processing needs

Note: Large HTML pages may trigger chat summarization in AI assistants due to content length limits. For complex pages, consider using specific root selectors to focus analysis on relevant sections.

Demo

To run the demo code, clone the repo, execute the ./packages/@progress/roadkill/example.ts by running:

npm run example

About @progress/roadkill/webdriver

The webdriver implementation steps on nodejs v18.17.1, uses TypeScript, and follows the webdriver spec as close as possible.

Each webdriver command, is implemented as a function or a class method, invoking an underlying fetch request. The parameters to these methods has been described 1:1 by the spec using TypeScript interfaces providing compile time type checking and smart suggestions in the VSCode IDE. The webdriver connection, session and web elements are wrapped in classes, for better encapsulation. Documentation of methods is linked to the respective webdriver protocol, so they can be easily 1:1 mapped. The library provides only Promise based async methods, and while opinionated, allows good transparency of what happens under the hood of your tests.

About @progress/roadkill/chromedriver

The ChromeDriver class can start a chromedriver locally, and listen for the chromedriver server to run on a localhost port. The service is also disposable shutting down the chromedriver process. Normally you will instantiate the ChromeDriver class and call const address = await chromedriver.start(); in beforeAll, connect the WebDriver client to the ChromeDriver address, and on afterAll call await chromedriver.dispose().

AbortSignal

Node.js 18.17.1 supports AbortSignals, and each command can accept as a last argument an abort signal. You can interrupt your tests midway and still get a good callstack.

Errors

Node.js now supports JavaScript hierarchical errors. See Errors with causes. This allows the API to wrap low-level errors with additional context and rethrow more meaningful high-level errors with full stack traces.

Example of a failing Find Element, showing developer friendly top-level error, and detailed web driver low-level error:

WebDriverMethodError: Failed to find element by css selector "missing child", from element.
    at Element.findElement (/Users/cankov/git/telerik/roadkill/webdriver.ts:998:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at <anonymous> (/Users/cankov/git/telerik/roadkill/index.ts:70:1) {
  lookup: { using: 'css selector', value: 'missing child' },
  [cause]: WebDriverRequestError: no such element: no such element: Unable to locate element: {"method":"css selector","selector":"missing child"}
    (Session info: chrome=116.0.5845.140)
      at WebDriverClient.request (/Users/cankov/git/telerik/roadkill/webdriver.ts:499:23)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at Element.findElement (/Users/cankov/git/telerik/roadkill/webdriver.ts:996:20)
      at <anonymous> (/Users/cankov/git/telerik/roadkill/index.ts:70:1) {
    address: 'http://localhost:9542',
    sessionId: 'ff751c52ea3d9c15b11ca55308f97152',
    elementId: 'C034F7771093BB6022C302E050CC399E_element_13'
  }
}

Example of a failing Print Page on Safari:

WebDriverMethodError: Failed to print page.
    at Session.printPage (/Users/cankov/git/telerik/roadkill/webdriver.ts:894:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at <anonymous> (/Users/cankov/git/telerik/roadkill/index.ts:89:24) {
  printOptions: { orientation: 'portrait', pageRanges: [ '1-2', '4-5' ] },
  [cause]: WebDriverRequestError: unknown command: The command 'POST /session/C751085A-3F88-452C-A0D1-B891FB9B0D34/print' was not found.
      at WebDriverClient.request (/Users/cankov/git/telerik/roadkill/webdriver.ts:499:23)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at Session.printPage (/Users/cankov/git/telerik/roadkill/webdriver.ts:892:20)
      at <anonymous> (/Users/cankov/git/telerik/roadkill/index.ts:89:24) {
    address: 'http://localhost:1550',
    sessionId: 'C751085A-3F88-452C-A0D1-B891FB9B0D34'
  }
}

Pros and Cons

TypeScript First

Today, 05 Oct 2023 I am using TypeScript for code type checking and auto completions. I was trying to find how a certain find method works on a famous automation framework. I mouse over the find in:

    /**
     * Locates a tab in the editor area by title containing the {@link title} string.
     * Note the search does not do a complete match, but looks for a substring.
     * @param title 
     * @returns 
     */
    public async waitForTab(title: string): Promise<void> {
        const tabLocator = By.css(`.tabs-and-actions-container .tab[aria-label*="${Escape.cssEscape(title)}"]`);
        await this.find(tabLocator, 20000);
    }

And then the VS Code IDE opens a private package in a web-app.d.ts file. I open the web-app.js by hand, go to find() only to see the transpiled TypeScript to JavaScript:

 find(locator, timeout = 10000, pollTimeout = 25) {
        return __awaiter(this, void 0, void 0, function* () {

Somewhere there is yield this.driver.wait(***_webdriver_1.until.elementLocated(locator), ctrl + click again, sent to node_modules/@types/<3rd-party>/<module.d.ts>. Navigate by hand again back from node_modules/@types/<3rd-party> to node_modules/<3rd-party>.

With a thin framework, like roadkill, targeting TypeScript, ctrl + click will send you to .ts code. And without multiple layers of abstraction, you won't get lost in polymorphic calls.

Imagine you could hold ctrl and point over your testing framework and see what exactly happens under-the-hood:

Compile-Time Checking

Today, 05 Oct 2023

A test of mine is flaky. The test fails, I capture a screenshot and attach that to the test artifacts.

 - Create Kendo UI for Angular Application › default theme

    TimeoutError: Failed to find element located by By(xpath, //h3[text()="Kendo UI Template Wizard"]).

    Wait timed out after 45021ms

Several other elements tests like this passed successfully. The screenshot shows the element visibly is there. And I am trying to find out why the find() method would fail.

There is so much type checking boilerplate:

    if (typeof timeout !== 'number' || timeout < 0) {
      throw TypeError('timeout must be a number >= 0: ' + timeout)
    }

With TypeScript you just annotate find(timeout: number) and focus on the application logic.

Get The Stack

Today, 14 Oct 2023

I am getting a test fail report from one of my e2e:

StaleElementReferenceError: stale element reference: stale element not found
      (Session info: chrome=114.0.5735.289)

      at Object.throwDecodedError (node_modules/selenium-webdriver/lib/error.js:524:15)
      at parseHttpResponse (node_modules/selenium-webdriver/lib/http.js:587:13)
      at Executor.execute (node_modules/selenium-webdriver/lib/http.js:515:28)
      at async Driver.execute (node_modules/selenium-webdriver/lib/webdriver.js:745:17)

What is wrong with it? None of the lines have anything to do with my code. This usually boils down to a "find + click" pair that execute in two separate requests to the WebDriver and in between dynamic UI had changed. If only the underlying framework wasn't using excessive amounts of abstractions.

So where did it fail? No idea!

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •