Custom Page Objects
When testing a VS Code extension, your extension contributes its own UI — custom tree views, webviews, toolbars, status bar items, and more. ExTester’s built-in page objects cover the VS Code shell, but not your extension-specific UI.
Custom page objects let you encapsulate that extension UI into reusable, typed classes — exactly the same way built-in page objects work — so your test code stays clean and readable.
When to use custom page objects
Section titled “When to use custom page objects”Use a custom page object whenever a test needs to interact with UI that your extension contributes and that is not covered by a built-in page object. Examples:
- A custom webview panel rendered by your extension
- A tree view contributed via
vscode.window.createTreeView - A custom status bar item with a specific click target
- Any element with an extension-specific CSS class or
data-attribute
How it works
Section titled “How it works”At startup, ExTester loads the version-specific built-in locators, then deep-merges your custom locator contribution on top. After that, any page object class that references MyClass.locators.MyComponent.xxx will resolve just like a built-in.
You provide:
- A locator contribution file — a compiled JS module that exports a
locatorsobject in the same shape as aLocatorDiff. - One or more page object classes — TypeScript classes extending
AbstractElementthat use those locators.
Step 1 — Write the locator contribution file
Section titled “Step 1 — Write the locator contribution file”Create a TypeScript file (e.g. src/test/pageObjects/locators.ts) that exports a locators object. The shape matches LocatorDiff.locators — one key per component, each key containing the selectors that component uses.
import { By } from "vscode-extension-tester";import type { LocatorDiff } from "vscode-extension-tester";
export const locators: LocatorDiff["locators"] = { MyExtensionToolbar: { constructor: By.className("my-extension-toolbar"), button: By.css(".my-extension-toolbar .action-btn"), label: By.css(".my-extension-toolbar .label"), },};Custom keys (like MyExtensionToolbar) are accepted without any extra TypeScript configuration. The LocatorDiff['locators'] type is intentionally open to unknown component names.
This file is compiled to JS as part of your normal test build (e.g. out/test/pageObjects/locators.js). The framework loads the compiled JS, not the TypeScript source.
Step 2 — Write the page object class
Section titled “Step 2 — Write the page object class”Create a class extending AbstractElement in its own file (e.g. src/test/pageObjects/MyExtensionToolbar.ts). Reference all selectors via MyExtensionToolbar.locators — never hardcode XPath or CSS strings in the class body.
import { AbstractElement } from "vscode-extension-tester";
export class MyExtensionToolbar extends AbstractElement { constructor() { super(MyExtensionToolbar.locators.MyExtensionToolbar.constructor); }
async clickButton(): Promise<void> { const btn = await this.findElement(MyExtensionToolbar.locators.MyExtensionToolbar.button); await btn.click(); }
async getLabel(): Promise<string> { const lbl = await this.findElement(MyExtensionToolbar.locators.MyExtensionToolbar.label); return lbl.getText(); }}Step 3 — Register the locator contribution
Section titled “Step 3 — Register the locator contribution”You must tell ExTester where to find the compiled locator contribution file. Do this via RunOptions (programmatic) or the CLI flag.
Programmatic API
Section titled “Programmatic API”Pass customPageObjects.locatorsPath in RunOptions:
import { ExTester } from "vscode-extension-tester";
const exTester = new ExTester();await exTester.runTests("out/test/**/*.test.js", { resources: [], customPageObjects: { locatorsPath: "./out/test/pageObjects/locators.js", },});A path passed via the CLI flag or RunOptions is resolved relative to the current working directory. A run.customPageObjects path declared inside extester.config.json is resolved relative to that config file’s directory.
Use the -p / --custom_page_objects flag on run-tests or setup-and-run:
extest run-tests 'out/test/**/*.test.js' \ --custom_page_objects ./out/test/pageObjects/locators.jsextest setup-and-run 'out/test/**/*.test.js' \ --custom_page_objects ./out/test/pageObjects/locators.jsConfig file
Section titled “Config file”The option can also be set via run.customPageObjects in extester.config.json — see Using a Config File:
{ "run": { "customPageObjects": "./out/test/pageObjects/locators.js" }}Step 4 — Use the class in a test
Section titled “Step 4 — Use the class in a test”Import and instantiate the class directly. No registry lookup needed.
import { MyExtensionToolbar } from "../pageObjects/MyExtensionToolbar";
describe("MyExtension toolbar", () => { it("shows the action button", async () => { const toolbar = new MyExtensionToolbar(); const label = await toolbar.getLabel(); expect(label).to.equal("My Action"); });});Full example layout
Section titled “Full example layout”src/ test/ pageObjects/ locators.ts ← Step 1: locator contribution file (compiled → out/) MyExtensionToolbar.ts ← Step 2: page object class myExtension.test.ts ← Step 4: test file- The locator contribution is a deep merge — you can safely add new component keys alongside the built-in locators without affecting them.
- Only one locator contribution file is supported per test run. Put all custom component locators in that single file.
- The locator contribution file must export a property named
locatorsat the module root. Note that the built-in version-specific locator files exportdiff— but custom contribution files must exportlocators, a common mistake. - Page object classes do not need to be registered anywhere — import and use them directly.
