Claude
Skills
Sign in
Back

e2e-testing

Included with Lifetime
$97 forever

End-to-end testing for Falcon Foundry apps using Playwright and @crowdstrike/foundry-playwright. TRIGGER when user asks to "add e2e tests", "add playwright tests", "write end-to-end tests", "test my app", or mentions "e2e", "playwright", or "end-to-end" in the context of testing a Foundry app. DO NOT TRIGGER during normal app creation, UI development, or function development. This skill is opt-in; not all apps need e2e tests.

Designfoundrye2eplaywrighttesting

What this skill does


# Foundry E2E Testing

End-to-end testing for Falcon Foundry apps using [Playwright](https://playwright.dev/) and the [`@crowdstrike/foundry-playwright`](https://github.com/CrowdStrike/foundry-playwright) library.

The library provides authentication, app install/uninstall, page objects, and configuration so each app only writes its app-specific tests.

## Quick Start

### 1. Create the `e2e/` directory

```
my-foundry-app/
├── e2e/
│   ├── .env                    # Local credentials (git-ignored)
│   ├── .env.sample             # Template for other developers
│   ├── .gitignore
│   ├── package.json
│   ├── playwright.config.ts
│   └── tests/
│       └── foundry.spec.ts
├── manifest.yml
└── ...
```

### 2. `package.json`

Node.js LTS is recommended.

```json
{
  "name": "playwright-foundry",
  "version": "1.0.0",
  "scripts": {
    "test": "npx playwright test",
    "test:ui": "npx playwright test --ui",
    "test:debug": "npx playwright test --debug",
    "test:verbose": "DEBUG=true npx playwright test --reporter=list"
  },
  "type": "commonjs",
  "devDependencies": {
    "@crowdstrike/foundry-playwright": "0.5.0",
    "@types/node": "25.6.0"
  }
}
```

**Always pin exact versions** — never use `"latest"`, `"^"`, or `"~"`. Check npm for the current version of each package.

The library brings `@playwright/test`, `@dotenvx/dotenvx`, and `otpauth` as transitive dependencies. No need to install them separately.

### 3. `.env`

```sh
[email protected]
FALCON_PASSWORD=your-password
FALCON_AUTH_SECRET=your-totp-secret
FALCON_BASE_URL=https://falcon.us-2.crowdstrike.com
APP_NAME=your-app-name
```

**Convention for sample apps:** Set `APP_NAME` to match the manifest `name` field, which should match the repo name (e.g., `foundry-sample-functions-python`). This avoids spaces in names and simplifies CI. This is a convention, not a hard requirement.

### 4. `playwright.config.ts`

```typescript
import { defineFoundryConfig } from '@crowdstrike/foundry-playwright';

export default defineFoundryConfig();
```

This gives you the standard 4-project pipeline automatically:
1. **setup**: authenticate and save session state
2. **app-install**: install the app via App Catalog
3. **chromium**: run your tests
4. **app-uninstall**: clean up after tests

### 5. `.gitignore`

```
node_modules/
playwright/.auth/
playwright-report/
test-results/
.env
```

### 6. Install and run

```bash
cd e2e
npm install
npx playwright install chromium --with-deps
npm test
```

## Writing Tests

### Available page objects

The library provides these page objects:

| Class | Purpose |
|-------|---------|
| `WorkflowsPage` | Search, open, execute, and verify Falcon Fusion SOAR workflows |
| `DetectionExtensionPage` | Navigate to Endpoint Detections, expand extensions, return iframe FrameLocator |
| `HostManagementPage` | Navigate to host management, retrieve host IDs |
| `AppCatalogPage` | Install, uninstall, and navigate to apps |
| `AppBuilderPage` | Disable workflow provisioning before install |
| `AppManagerPage` | Find and navigate to apps in App Manager |
| `FoundryHomePage` | Navigate to Falcon Foundry home |

### Fixtures pattern

Create `src/fixtures.ts` to wire up page objects as Playwright fixtures. **Only import what your tests actually use** — don't define unused fixtures:

```typescript
import { test as baseTest } from '@playwright/test';
import { DetectionExtensionPage, WorkflowsPage } from '@crowdstrike/foundry-playwright';

type FoundryFixtures = {
  detectionExtensionPage: DetectionExtensionPage;
  workflowsPage: WorkflowsPage;
};

export const test = baseTest.extend<FoundryFixtures>({
  detectionExtensionPage: async ({ page }, use) => { await use(new DetectionExtensionPage(page)); },
  workflowsPage: async ({ page }, use) => { await use(new WorkflowsPage(page)); },
});

export { expect } from '@playwright/test';
```

Playwright fixtures are lazy (only instantiated when a test requests them), so unused fixtures don't hurt performance — but they add confusion and dead code. Add fixtures as you add tests that need them.

### Example test: workflows

```typescript
import { test } from '../src/fixtures';

test.describe.configure({ mode: 'serial' });

test('should execute workflow', async ({ workflowsPage }) => {
  test.setTimeout(180000);
  await workflowsPage.navigateToWorkflows();
  await workflowsPage.executeAndVerifyWorkflow('My Workflow Name');
  await workflowsPage.verifyWorkflowExecutionCompleted();
});

test('should execute workflow with input', async ({ workflowsPage, hostManagementPage }) => {
  test.setTimeout(180000);
  const hostId = await hostManagementPage.getFirstHostId();
  if (!hostId) { test.skip(true, 'No hosts available'); return; }

  await workflowsPage.navigateToWorkflows();
  await workflowsPage.executeAndVerifyWorkflow('Host Details Workflow', {
    inputs: { 'Host ID': hostId },
  });
  await workflowsPage.verifyWorkflowExecutionCompleted();
});
```

`executeAndVerifyWorkflow()` handles search, execution trigger, and initial verification. `verifyWorkflowExecutionCompleted()` opens the execution detail view in a new tab and polls until the status leaves "In Progress" — it fails the test if the execution reports "Failed" and times out after 120s by default. For render-only checks (e.g., ServiceNow workflows without credentials), use `verifyWorkflowRenders()`.

### Example test: UI extensions

```typescript
import { test, expect } from '../src/fixtures';

test('should render extension', async ({ detectionExtensionPage }) => {
  const frame = await detectionExtensionPage.openExtension('hello');
  await expect(frame.getByText(/My App Title/i)).toBeVisible({ timeout: 10000 });
});
```

`openExtension()` navigates to Endpoint Detections, opens the first detection, scrolls to the named extension button, expands it, and returns the iframe FrameLocator.

## Apps with Configuration Screens

If your app has API integration settings during install (e.g., ServiceNow credentials), the default install will fail because the Install button stays disabled until fields are filled.

### 1. Add integration credentials to `.env` and `.env.sample`

```sh
# .env.sample — commit this as a template
SERVICENOW_INSTANCE_URL=https://dev123456.service-now.com
SERVICENOW_USERNAME=your-servicenow-username
SERVICENOW_PASSWORD=your-servicenow-password

# .env — local values, git-ignored
SERVICENOW_INSTANCE_URL=https://dev99999.service-now.com
SERVICENOW_USERNAME=admin
SERVICENOW_PASSWORD=s3cret
```

### 2. Create a custom `tests/app-install.setup.ts`

```typescript
import { test as setup } from '@playwright/test';
import { AppCatalogPage, config } from '@crowdstrike/foundry-playwright';

setup('install app', async ({ page }) => {
  const catalog = new AppCatalogPage(page);

  const instanceUrl = process.env.SERVICENOW_INSTANCE_URL;
  const username = process.env.SERVICENOW_USERNAME;
  const password = process.env.SERVICENOW_PASSWORD;
  if (!instanceUrl || !username || !password) {
    throw new Error('Missing required ServiceNow env vars: SERVICENOW_INSTANCE_URL, SERVICENOW_USERNAME, SERVICENOW_PASSWORD');
  }

  await catalog.installApp(config.appName, {
    configureSettings: async (page) => {
      await page.getByRole('textbox', { name: 'Name', exact: true }).fill('ServiceNow Integration');
      await page.getByRole('textbox', { name: 'Instance' }).fill(instanceUrl);
      await page.getByRole('textbox', { name: 'Username' }).fill(username);
      await page.getByRole('textbox', { name: 'Password' }).fill(password);
    },
  });
});
```

The library loads `.env` automatically (via `@dotenvx/dotenvx`) so `process.env` values are available without extra setup. In CI, set these as GitHub Actions secrets instead.

### 3. Point the config at the custom install

```typescript
export default defineFoundryConfig({
  appInstallDir: './tests',
});
```

**How to discover field names:** Use Playwright MCP to take a snapshot of the install page and inspect the form fields
Files: 2
Size: 25.4 KB
Complexity: 46/100
Category: Design

Related in Design