Skip to content
Draft
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
48 changes: 48 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,51 @@ jobs:

- name: Test
run: xvfb-run -a pnpm vscode-test

test-e2e:
name: Test (e2e)
runs-on: ubuntu-latest
# Fork PRs never have access to repository secrets (LOCALSTACK_AUTH_TOKEN
# below), so this job is skipped rather than failed for them; it still
# runs for same-repo branches/pushes. The event_name check keeps this
# from misfiring on non-pull_request triggers.
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository

steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Setup pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
# Pinned explicitly (unlike the other jobs) because wdio-vscode-service's
# WebSocket handshake with the launched Extension Development Host is
# sensitive to the Node net module's connection behavior — confirmed
# during local verification that a too-new Node major (26) breaks it,
# while this repo's own .node-version (24) works.
node-version-file: .node-version
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Install lstk
run: npm install -g @localstack/lstk

- name: Setup Terraform
uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
with:
# The wrapper script exists to capture stdout/stderr into this
# action's own step outputs, which nothing here reads; `lstk tf`
# just needs a plain `terraform` binary on PATH.
terraform_wrapper: false

- name: Test (e2e)
run: xvfb-run -a pnpm test:e2e
env:
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
/*.vsix
/.env
/idea
.wdio-vscode-service
/test/e2e/logs/
/test/e2e/fixtures/terraform/**/.terraform/
/test/e2e/fixtures/terraform/**/.terraform.lock.hcl
/test/e2e/fixtures/terraform/**/terraform.tfstate*
/test/e2e/fixtures/terraform/**/*.zip
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
14 changes: 14 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ export default defineConfig(
],
},
},
{
// The root tsconfig.json excludes test/ and wdio.conf.ts (so `tsc`/
// `check-types` doesn't try to compile the e2e suite as part of the
// extension itself), so the projectService above never finds a project
// covering these files. Point it at their own tsconfig instead.
files: ["test/e2e/**/*.ts", "wdio.conf.ts"],
languageOptions: {
parserOptions: {
projectService: false,
project: ["./test/e2e/tsconfig.json"],
tsconfigRootDir: import.meta.dirname,
},
},
},
{
extends: [
importPlugin.flatConfigs.recommended,
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/add-webdriverio-e2e-testing/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-12
107 changes: 107 additions & 0 deletions openspec/changes/add-webdriverio-e2e-testing/design.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions openspec/changes/add-webdriverio-e2e-testing/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Why

The extension currently has only an in-process `vscode-test` suite (`pnpm test`), which drives the `vscode` API directly and cannot exercise the real rendered UI — tree views, webviews, Quick Picks — as a user actually sees them. Before we can write extensive end-to-end coverage for the Resources view / Resource Details panel (and other UI-facing behavior), we need a framework capable of driving a genuine Extension Development Host through WebDriver. This change adds that framework, wired into CI, with a single demonstration suite proving the whole stack fits together end-to-end.

## What Changes

- Add a `test:e2e` pnpm target that runs WebdriverIO (`@wdio/cli`, `@wdio/local-runner`, `@wdio/mocha-framework`) with `wdio-vscode-service` against a real Extension Development Host, configured via a new `wdio.conf.ts`.
- Add `test/e2e/` housing the e2e suite: a `support/lstk.ts` helper that shells out to the `lstk` CLI (`start`/`stop`/`tf init`/`tf apply`/`tf destroy`), a Terraform fixture under its own named subdirectory (`test/e2e/fixtures/terraform/small-serverless/`, leaving room for sibling fixtures as more suites land), an empty workspace fixture the Extension Development Host opens, and a `specs/` directory of `*.e2e.ts` suites.
- Add a demonstration suite (`resource-browser.e2e.ts`) that starts LocalStack, deploys a small wired application via Terraform — an SNS topic fanning out to an SQS queue, which triggers a Lambda function (via an event source mapping) running under a dedicated IAM role, plus an S3 bucket referenced by the function — enables the resource browser opt-in, walks the Resources tree to confirm exactly those resources appear, and opens Resource Details for each to confirm the expected field sets and values. Each resource carries up to three explicitly-set non-default field values (fewer where the resource's Resource Details view doesn't expose that many configurable fields — see design.md).
- Add a new GitHub Actions job (in `.github/workflows/build.yml`, or a new workflow file) that runs `test:e2e` on every pull request, using `xvfb-run` the same way the existing `Test` job does for `vscode-test`.
- Supporting config: exclude `test/` and `wdio.conf.ts` from the root `tsconfig.json`'s type-checked set (with their own `test/e2e/tsconfig.json`), point ESLint's `test/e2e/**/*.ts` + `wdio.conf.ts` files at that tsconfig, ignore e2e run artifacts (`.wdio-vscode-service`, `test/e2e/logs/`, Terraform state/lock/plugin files) in `.gitignore`, and pin/allow the `wdio`-related packages' install-time trust checks in `pnpm-workspace.yaml`.

This PR delivers only the framework and one demonstration suite, not the full backlog of e2e coverage described in `openspec/specs/resource-browser/spec.md`.

## Capabilities

### New Capabilities
- `e2e-testing`: WebdriverIO + `wdio-vscode-service` framework for driving the extension's real rendered UI in a genuine Extension Development Host, plus the `lstk`/Terraform fixture conventions e2e suites use to stand up LocalStack state, and the CI job that runs it on every PR.

### Modified Capabilities
- `resource-browser`: this change's own verification (running the demonstration suite against a machine with no `~/.aws/config`) surfaced a production bug — the default `localstack` profile's identity and every LocalStack resource-listing SDK call depended on `~/.aws/config` having a matching section, so a fresh install showed duplicate, errored `Profile: localstack` rows and an empty Resources tree. Fixed in `AWSConfig.getClientConfig` (see design.md's Decisions); no requirement text changed, since the shipped spec never explicitly sanctioned the buggy behavior.

## Impact

- **New dependencies** (devDependencies): `@wdio/cli`, `@wdio/local-runner`, `@wdio/mocha-framework`, `@wdio/spec-reporter`, `expect-webdriverio`, `wdio-vscode-service`, `webdriverio`, `tsx`.
- **New files**: `wdio.conf.ts`, `test/e2e/**` (support helper, fixtures — including a small prebuilt Lambda deployment package — specs, `tsconfig.json`, `global.d.ts`).
- **Modified files**: `package.json` (scripts + devDependencies), `tsconfig.json` (exclude e2e sources), `eslint.config.mjs` (separate parser project for e2e sources), `.gitignore` (ignore e2e run artifacts), `pnpm-workspace.yaml` (trust/build-script policy for wdio's transitive deps), `.github/workflows/build.yml` (new CI job), `src/platforms/aws/models/awsConfig.ts` and `src/platforms/aws/models/metamodelFocus.ts` (LocalStack profile resolution bug fix), `src/utils/localstack-endpoint.ts` (exported a shared constant), `src/test/models/awsConfig.test.ts` (regression tests for the fix).
- **CI runtime**: adds a job per PR that shells out to `lstk start`/`lstk tf apply` and drives a real Extension Development Host — meaningfully slower than the existing in-process `vscode-test` job, and requires the `lstk` CLI and Terraform to be available in the CI runner image.
- **Production/runtime code change**: `AWSConfig.getClientConfig` now resolves every LocalStack-managed profile's endpoint/credentials statically rather than reading `~/.aws/config` — a bug fix this change's own verification surfaced, not scope creep (see Modified Capabilities above and design.md's Decisions).
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
## ADDED Requirements

### Requirement: `test:e2e` pnpm target
The project SHALL provide a `pnpm test:e2e` target that compiles the extension and runs the WebdriverIO test runner against a real VS Code Extension Development Host, independent of the existing `pnpm test` (`vscode-test`) in-process suite.

#### Scenario: Running the target locally
- **WHEN** a developer runs `pnpm test:e2e` in a checkout with `lstk` and Docker available
- **THEN** the extension is compiled, a WebdriverIO session boots a real Extension Development Host with the extension loaded, every spec under `test/e2e/specs/**/*.e2e.ts` runs, and a spec-style report is printed to the console

#### Scenario: Existing in-process suite is unaffected
- **WHEN** a developer runs `pnpm test`
- **THEN** it continues to run only the `vscode-test` suite from `out/test/**/*.test.js`, unaffected by the addition of `test:e2e`

### Requirement: LocalStack and fixture lifecycle via the `lstk` CLI
E2e suites SHALL manage LocalStack and deployed test resources by shelling out to the `lstk` CLI directly (`start`, `stop`, `terraform init`, `terraform apply`, `terraform destroy`), not through the extension's own LocalStack start/stop commands.

#### Scenario: Suite setup starts LocalStack and deploys fixture resources
- **WHEN** a suite's `suiteSetup` runs
- **THEN** it invokes `lstk start` and waits for it to complete before invoking `lstk tf init` and `lstk tf apply -auto-approve` against that suite's Terraform fixture directory, and a non-zero exit from any of these raises an error including captured stdout/stderr

#### Scenario: Suite teardown tears down fixture resources and stops LocalStack
- **WHEN** a suite's `suiteTeardown` runs, including after a failed assertion
- **THEN** it invokes `lstk tf destroy -auto-approve` against the suite's fixture directory followed by `lstk stop`, and a failure in either step does not prevent the other from being attempted

### Requirement: Demonstration suite fixture models a wired small application
A demonstration e2e suite SHALL deploy a single Terraform fixture representing a small wired application across all five resource types named in DEVX-987: an SNS topic, an SQS queue, a Lambda function, an IAM role, and an S3 bucket. The SNS topic SHALL have a subscription delivering to the SQS queue; the SQS queue SHALL trigger the Lambda function via an event source mapping; the Lambda function SHALL run under the IAM role. Each resource SHALL carry as many explicitly-set non-default field values as its Resource Details view exposes as configurable, up to a maximum of three, and never fewer than that maximum supports.

#### Scenario: Fixture resources are wired, not independent
- **WHEN** the fixture has been applied
- **THEN** the deployed SNS topic has a confirmed subscription delivering to the deployed SQS queue, and the deployed Lambda function's role is the deployed IAM role

### Requirement: Demonstration suite validates Resources tree completeness
The demonstration suite SHALL assert that the Resources tree — after enabling the resource browser opt-in and refreshing — shows exactly the fixture's deployed resources under exactly the expected profile/region/service rows, with no missing or stray entries, including the IAM role's current (region-collapsed) placement.

#### Scenario: Tree shows exactly the deployed resources
- **WHEN** the fixture has been applied and the Resources view has been refreshed
- **THEN** the tree shows exactly one region (matching the fixture's configured region), exactly the services/resource-types `IAM`/`Roles`, `Lambda`/`Functions`, `S3`/`Buckets`, `SNS`/`Topics`, and `SQS`/`Queues` under it, and exactly the fixture's one resource under each

#### Scenario: IAM's role is asserted at its current tree placement
- **WHEN** the fixture's IAM role is located in the tree
- **THEN** it appears under the same single region row as every other deployed resource, reflecting the tree-building code's current behavior rather than a dedicated region-less/"global" placement (which does not exist in the tree-building code as of this change)

### Requirement: Lambda event source mappings are not discoverable through the Resources view
Because LocalStack's metamodel snapshot does not record the `listEventSourceMappings` operation (confirmed directly against a running instance), the Resources view's Default View auto-discovery SHALL NOT include Lambda event source mappings as a resource type regardless of whether one exists and is `Enabled`. The demonstration suite SHALL NOT assert on this resource type's presence in the tree or its Resource Details; the fixture still deploys a real event source mapping as genuine SQS→Lambda wiring, independent of whether this suite can observe it.

#### Scenario: Event source mapping row never appears in the Resources tree
- **WHEN** the fixture's Lambda event source mapping exists and has reached `State: Enabled`
- **THEN** the Resources tree's `Lambda` service shows only the `Functions` resource type, never `Event Source Mappings`, regardless of how many times the view is refreshed

### Requirement: Demonstration suite validates Resource Details field sets and values
The demonstration suite SHALL open the Resource Details panel for each deployed resource and assert both the exact set of field names shown and the correctness of field values, including each resource's explicitly-set non-default values.

#### Scenario: S3 bucket details show correct fields and values
- **WHEN** the deployed S3 bucket is selected in the Resources tree
- **THEN** Resource Details shows exactly the fields `ARN`, `Service`, `Name`, `Creation Date`, with `ARN` and `Name` matching the fixture's bucket, `Service` equal to `S3`, and `Creation Date` a parseable date

#### Scenario: SQS queue details show correct fields, including explicitly-set values
- **WHEN** the deployed SQS queue is selected in the Resources tree
- **THEN** Resource Details shows exactly the expected SQS field set, with `ARN`/`Name`/`Service` matching the fixture and the queue, and the fixture's explicitly-configured `Visibility Timeout`, `Message Retention Period`, and `Delay Seconds` values shown exactly as configured

#### Scenario: SNS topic details show correct fields, including explicitly-set values
- **WHEN** the deployed SNS topic is selected in the Resources tree
- **THEN** Resource Details shows exactly the expected SNS field set, with `Name`/`Service` matching the fixture and the topic, and the fixture's explicitly-configured `Display Name` shown exactly as configured

#### Scenario: Lambda function details show correct fields, including explicitly-set values
- **WHEN** the deployed Lambda function is selected in the Resources tree
- **THEN** Resource Details shows exactly the expected Lambda function field set, with `Name`/`Service`/`Role` matching the fixture and the function's IAM role, and the fixture's explicitly-configured `Description`, `Memory Size`, and `Timeout` values shown exactly as configured

#### Scenario: IAM role details show correct fields, including explicitly-set values
- **WHEN** the deployed IAM role is selected in the Resources tree
- **THEN** Resource Details shows exactly the expected IAM role field set, with `Role Name` matching the fixture, and the fixture's explicitly-configured `Path`, `Description`, and `Max Session Duration` values shown exactly as configured

### Requirement: E2e suite runs in CI on every pull request
A GitHub Actions job SHALL run `pnpm test:e2e` for every pull request targeting the main branch, in addition to (not replacing) the existing build/format/lint/type-check/test jobs.

#### Scenario: Pull request triggers the e2e job
- **WHEN** a pull request is opened or updated against the main branch
- **THEN** a CI job runs `pnpm test:e2e` (via a headless-display wrapper, matching the existing `test` job's approach) and the pull request's checks reflect its pass/fail result

### Requirement: E2e sources are isolated from the extension's own type-check and lint
Files under `test/e2e/` and `wdio.conf.ts` SHALL be excluded from the root TypeScript project (`pnpm tsc` / the `Type Check` CI job) and instead type-check and lint against their own dedicated TypeScript project.

#### Scenario: Root type-check ignores e2e sources
- **WHEN** `pnpm tsc` runs against the root `tsconfig.json`
- **THEN** it does not attempt to compile or type-check any file under `test/e2e/` or `wdio.conf.ts`

#### Scenario: E2e sources still lint and type-check under their own project
- **WHEN** `pnpm eslint` runs
- **THEN** files under `test/e2e/**/*.ts` and `wdio.conf.ts` are linted using `test/e2e/tsconfig.json` as their parser project, rather than being skipped or matched against the root project
Loading