Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 108 additions & 94 deletions javascript/atoms/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,177 +1,191 @@
load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary")
load("@aspect_rules_js//npm:defs.bzl", "npm_package")
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
load("@rules_closure//closure:defs.bzl", "closure_js_library")
load("//javascript:defs.bzl", "closure_js_deps", "closure_test_suite")

package(default_visibility = ["//visibility:public"])

VERSION = "4.51.0-nightly202609302012"

# These build tools are CommonJS; the .cjs extension keeps that true regardless
# of the "type": "module" declared by the @seleniumhq/atoms package.json above
# them in this directory.
js_binary(
name = "strip_trailing_semicolon",
data = ["typescript/strip-trailing-semicolon.js"],
entry_point = "typescript/strip-trailing-semicolon.js",
name = "cjs_to_iife",
data = ["typescript/cjs-to-iife.cjs"],
entry_point = "typescript/cjs-to-iife.cjs",
)

js_binary(
name = "wrap_get_attribute_as_global",
data = ["typescript/wrap-get-attribute-as-global.js"],
entry_point = "typescript/wrap-get-attribute-as-global.js",
data = ["typescript/wrap-get-attribute-as-global.cjs"],
entry_point = "typescript/wrap-get-attribute-as-global.cjs",
)

js_run_binary(
js_binary(
name = "wrap_as_global",
data = ["typescript/wrap-as-global.cjs"],
entry_point = "typescript/wrap-as-global.cjs",
)

js_binary(
name = "wrap_find_elements_as_global",
data = ["typescript/wrap-find-elements-as-global.cjs"],
entry_point = "typescript/wrap-find-elements-as-global.cjs",
)

# ── get-attribute ────────────────────────────────────────────────────────────

ts_project(
name = "get-attribute-typescript-compiled",
srcs = ["typescript/get-attribute.ts"],
outs = ["typescript/get-attribute.compiled.js"],
args = [
"--target",
"ES2017",
"--module",
"none",
"--moduleResolution",
"node",
# These atoms are self-contained and import nothing; pin typeRoots to the (type-free)
# source dir so unrelated ambient @types packages in node_modules cannot break the build.
"--typeRoots",
"javascript/atoms/typescript",
"--removeComments",
"--pretty",
"false",
"--outFile",
"$(rootpath :typescript/get-attribute.compiled.js)",
"$(rootpath :typescript/get-attribute.ts)",
],
tool = "@npm_typescript//:tsc",
declaration = False,
source_map = False,
tsconfig = "typescript/tsconfig-get-attribute.json",
)

js_run_binary(
name = "get-attribute-typescript-generated",
srcs = [":get-attribute-typescript-compiled"],
srcs = [":typescript/get-attribute.js"],
outs = ["typescript/get-attribute.generated.js"],
args = [
"$(rootpath :get-attribute-typescript-compiled)",
"$(rootpath :typescript/get-attribute.js)",
"$(rootpath :typescript/get-attribute.generated.js)",
],
tool = ":strip_trailing_semicolon",
tool = ":cjs_to_iife",
)

js_run_binary(
name = "get-attribute-global",
srcs = [":get-attribute-typescript-compiled"],
srcs = [":typescript/get-attribute.js"],
outs = ["typescript/get-attribute-global.js"],
args = [
"$(rootpath :get-attribute-typescript-compiled)",
"$(rootpath :typescript/get-attribute.js)",
"$(rootpath :typescript/get-attribute-global.js)",
],
tool = ":wrap_get_attribute_as_global",
)

js_binary(
name = "wrap_as_global",
data = ["typescript/wrap-as-global.js"],
entry_point = "typescript/wrap-as-global.js",
)
# ── find-elements ────────────────────────────────────────────────────────────

js_binary(
name = "wrap_find_elements_as_global",
data = ["typescript/wrap-find-elements-as-global.js"],
entry_point = "typescript/wrap-find-elements-as-global.js",
)

js_run_binary(
ts_project(
name = "find-elements-typescript-compiled",
srcs = ["typescript/find-elements.ts"],
outs = ["typescript/find-elements.compiled.js"],
args = [
"--target",
"ES2017",
"--module",
"none",
"--moduleResolution",
"node",
# These atoms are self-contained and import nothing; pin typeRoots to the (type-free)
# source dir so unrelated ambient @types packages in node_modules cannot break the build.
"--typeRoots",
"javascript/atoms/typescript",
"--removeComments",
"--pretty",
"false",
"--outFile",
"$(rootpath :typescript/find-elements.compiled.js)",
"$(rootpath :typescript/find-elements.ts)",
],
tool = "@npm_typescript//:tsc",
declaration = False,
source_map = False,
tsconfig = "typescript/tsconfig-find-elements.json",
)

js_run_binary(
name = "find-elements-typescript-generated",
srcs = [":find-elements-typescript-compiled"],
srcs = [":typescript/find-elements.js"],
outs = ["typescript/find-elements.generated.js"],
args = [
"$(rootpath :find-elements-typescript-compiled)",
"$(rootpath :typescript/find-elements.js)",
"$(rootpath :typescript/find-elements.generated.js)",
],
tool = ":strip_trailing_semicolon",
tool = ":cjs_to_iife",
)

js_run_binary(
name = "find-elements-global",
srcs = [":find-elements-typescript-compiled"],
srcs = [":typescript/find-elements.js"],
outs = ["typescript/find-elements-global.js"],
args = [
"$(rootpath :find-elements-typescript-compiled)",
"$(rootpath :typescript/find-elements.js)",
"$(rootpath :typescript/find-elements-global.js)",
],
tool = ":wrap_find_elements_as_global",
)

js_run_binary(
# ── is-displayed ─────────────────────────────────────────────────────────────

ts_project(
name = "is-displayed-typescript-compiled",
srcs = ["typescript/is-displayed.ts"],
outs = ["typescript/is-displayed.compiled.js"],
args = [
"--target",
"ES2017",
"--module",
"none",
"--moduleResolution",
"node",
# These atoms are self-contained and import nothing; pin typeRoots to the (type-free)
# source dir so unrelated ambient @types packages in node_modules cannot break the build.
"--typeRoots",
"javascript/atoms/typescript",
"--removeComments",
"--pretty",
"false",
"--outFile",
"$(rootpath :typescript/is-displayed.compiled.js)",
"$(rootpath typescript/is-displayed.ts)",
],
tool = "@npm_typescript//:tsc",
declaration = False,
source_map = False,
tsconfig = "typescript/tsconfig-is-displayed.json",
)

js_run_binary(
name = "is-displayed-typescript-generated",
srcs = [":is-displayed-typescript-compiled"],
srcs = [":typescript/is-displayed.js"],
outs = ["typescript/is-displayed.generated.js"],
args = [
"$(rootpath :is-displayed-typescript-compiled)",
"$(rootpath :typescript/is-displayed.js)",
"$(rootpath :typescript/is-displayed.generated.js)",
],
tool = ":strip_trailing_semicolon",
tool = ":cjs_to_iife",
)

js_run_binary(
name = "is-displayed-global",
srcs = [":is-displayed-typescript-compiled"],
srcs = [":typescript/is-displayed.js"],
outs = ["typescript/is-displayed-global.js"],
args = [
"$(rootpath :is-displayed-typescript-compiled)",
"$(rootpath :typescript/is-displayed.js)",
"$(rootpath :typescript/is-displayed-global.js)",
],
tool = ":wrap_as_global",
)

# ── @seleniumhq/atoms npm package ────────────────────────────────────────────

ts_project(
name = "atoms-npm-compiled",
srcs = [
"index.ts",
"typescript/find-elements.ts",
"typescript/get-attribute.ts",
"typescript/is-displayed.ts",
],
declaration = True,
declaration_map = True,
out_dir = "dist",
source_map = True,
tsconfig = "tsconfig.json",
)

# npm_package does not pick up filegroup sources.
genrule(
name = "license",
srcs = ["//:license"],
outs = [
"LICENSE",
"NOTICE",
],
cmd = "cp $(locations //:license) $(@D)",
)

npm_package(
name = "selenium-atoms-package",
srcs = [
# README.md in this directory is the contributor guide; the npm page
# gets its own file, renamed into place below.
"README.npm.md",
# Published so the emitted .d.ts.map / .js.map resolve for consumers.
"index.ts",
"package.json",
"typescript/find-elements.ts",
"typescript/get-attribute.ts",
"typescript/is-displayed.ts",
":atoms-npm-compiled",
":license",
],
package = "@seleniumhq/atoms",
publishable = True,
replace_prefixes = {"README.npm.md": "README.md"},
tags = ["release-artifact"],
version = VERSION,
visibility = ["//visibility:public"],
)

filegroup(
name = "atoms",
srcs = glob([
"**/*.cjs",
"**/*.html",
"**/*.jpg",
"**/*.js",
Expand Down
6 changes: 6 additions & 0 deletions javascript/atoms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ These "atoms" provide reusable building blocks for browser automation
Google Closure Compiler, but at some point, we'd love to migrate them
to TypeScript since Closure isn't as widely known.

The atoms that have already been migrated to TypeScript are additionally
published to npm as `@seleniumhq/atoms`, for tooling outside the Selenium
bindings that needs to match Selenium's behaviour. `README.npm.md` is the
consumer-facing documentation for that package; this file is for contributors
working on the atoms themselves.

## Testing the Atoms

### Iteratively
Expand Down
65 changes: 65 additions & 0 deletions javascript/atoms/README.npm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# @seleniumhq/atoms

Browser injection atoms for Selenium WebDriver, published as TypeScript-typed
ES modules.

Atoms are small, self-contained functions that run inside the page under
automation. They encode the browser-compatibility behaviour that the W3C
WebDriver specification leaves to implementations β€” what "displayed" means, how
`getAttribute` reconciles attributes with IDL properties, and how a locator
resolves to elements. Selenium's own language bindings inject the same logic,
compiled to an IIFE; this package exposes it to other JavaScript tooling that
needs to match Selenium's behaviour exactly.

## Installation

```sh
npm install @seleniumhq/atoms
```

This is an ES-module-only package and requires Node.js 18 or newer.

## Usage

```js
import { getAttribute, findElements, isDisplayed } from '@seleniumhq/atoms'
```

Each atom is also available as its own subpath export, so a bundler can tree
shake down to just the one you need:

```js
import getAttribute from '@seleniumhq/atoms/get-attribute'
import findElements from '@seleniumhq/atoms/find-elements'
import isDisplayed from '@seleniumhq/atoms/is-displayed'
```

### API

```ts
getAttribute(element: Element, attribute: string): string | null
findElements(target: Record<string, unknown>, root?: Document | Element | ShadowRoot): Element[]
isDisplayed(element: Element, ignoreOpacity?: boolean): boolean
```

`findElements` takes a locator object keyed by strategy, matching the WebDriver
wire format:

```js
findElements({ css: '.result' })
findElements({ 'link text': 'Continue' }, shadowRoot)
```

The atoms operate on the DOM of the document they are evaluated in, so they must
run in the page rather than in the driver process β€” for example via
`Runtime.evaluate` over CDP, `script.callFunction` over WebDriver BiDi, or
Selenium's own `executeScript`.

## Versioning

This package is released from the Selenium monorepo and shares its version with
the `selenium-webdriver` npm package.

## License

Apache-2.0. See `LICENSE` and `NOTICE`.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,9 @@
// specific language governing permissions and limitations
// under the License.

const fs = require('node:fs');

const [inputPath, outputPath] = process.argv.slice(2);

if (!inputPath || !outputPath) {
throw new Error('Expected input and output file paths');
}

const input = fs.readFileSync(inputPath, 'utf8');
const output = input.replace(/;\s*$/, '');
fs.writeFileSync(outputPath, output);
// Specifiers carry the emitted .js extension because Node's ESM resolver does
// not probe for extensions. "module": "esnext" rewrites nothing, so what is
// written here is what consumers resolve against.
export { default as getAttribute } from './typescript/get-attribute.js'
export { default as findElements } from './typescript/find-elements.js'
export { default as isDisplayed } from './typescript/is-displayed.js'
Loading
Loading