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
20 changes: 16 additions & 4 deletions src/platform/packages/shared/kbn-scout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,14 +190,14 @@ your-plugin/
│ ├── ui/
│ │ ├── playwright.config.ts
│ │ ├── parallel.playwright.config.ts
│ │ └── parallel_tests/ # Your UI test specs, that are run in parallel
│ │ └── tests/ # Your UI test specs, that are run sequentially
│ │ └── parallel_tests/ # Your UI test specs (*.spec.ts), that are run in parallel
│ │ └── tests/ # Your UI test specs (*.spec.ts), that are run sequentially
│ ├── api/
│ │ ├── playwright.config.ts
│ │ └── tests/ # Your API test specs, that are run sequentially
│ │ └── tests/ # Your API test specs (*.spec.ts), that are run sequentially
│ └── common/ # For shared code across UI and API tests
│ ├── constants.ts
│ └── fixtures/
│ └── fixtures/
```

#### UI Tests
Expand Down Expand Up @@ -346,6 +346,18 @@ To start the servers locally and run tests in one step, use:
node scripts/scout.js run-tests [--stateful|--serverless=[es|oblt|security]] --config <plugin-path>/test/scout/ui/playwright.config.ts
```

To start the servers locally and run a single test file, use:

```bash
node scripts/scout.js run-tests [--stateful|--serverless=[es|oblt|security]] --testFiles <plugin-path>/test/scout/ui/tests/your_test_spec.ts
```

To start the servers locally and run a tests sub-directory, use:

```bash
node scripts/scout.js run-tests [--stateful|--serverless=[es|oblt|security]] --testFiles <plugin-path>/test/scout/ui/tests/test_sub_directory
```

- **`--stateful`** or **`--serverless`**: Specifies the deployment type.
- **`--config`**: Path to the Playwright configuration file for the plugin.

Expand Down
2 changes: 2 additions & 0 deletions src/platform/packages/shared/kbn-scout/src/cli/run_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const runTestsCmd: Command<void> = {
Running tests against local servers:
node scripts/scout run-tests --stateful --config <playwright_config_path>
node scripts/scout run-tests --serverless=es --headed --config <playwright_config_path>
node scripts/scout run-tests --serverless=es --testFiles <spec_path1,spec_path2>
node scripts/scout run-tests --serverless=es --testFiles <spec_directory_path>
Running tests against Cloud deployment / MKI project:
node scripts/scout run-tests --stateful --testTarget=cloud --config <playwright_config_path>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ export const measurePerformanceAsync = async <T>(

return result;
};

export { validateAndProcessTestFiles, type TestFilesValidationResult } from './test_files';
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { validateAndProcessTestFiles } from './test_files';

// Mock the kbn-repo-info module to return a predictable REPO_ROOT
jest.mock('@kbn/repo-info', () => ({
REPO_ROOT: '/mock/repo/root',
}));

// Mock the fs module
jest.mock('fs', () => ({
existsSync: jest.fn(),
statSync: jest.fn(),
readdirSync: jest.fn(),
}));

import * as fs from 'fs';
const mockFs = fs as jest.Mocked<typeof fs>;

describe('validateAndProcessTestFiles', () => {
beforeEach(() => {
jest.clearAllMocks();

// Default mocks for successful validation
mockFs.existsSync.mockReturnValue(true);
mockFs.statSync.mockReturnValue({ isFile: () => true, isDirectory: () => false } as any);
});

describe('UI tests directory', () => {
it('should derive correct config for ui/tests directory', () => {
const testFile =
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests/test.spec.ts';

const result = validateAndProcessTestFiles(testFile);

expect(result).toEqual({
testFiles: [testFile],
configPath:
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/playwright.config.ts',
});
});

it('should derive correct config for ui/parallel_tests directory', () => {
const testFile =
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/parallel_tests/test.spec.ts';

const result = validateAndProcessTestFiles(testFile);

expect(result).toEqual({
testFiles: [testFile],
configPath:
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/parallel.playwright.config.ts',
});
});
});

describe('API tests directory', () => {
it('should derive correct config for scout/api/tests directory', () => {
const testFile =
'x-pack/solutions/observability/plugins/my_plugin/test/scout/api/tests/test.spec.ts';

const result = validateAndProcessTestFiles(testFile);

expect(result).toEqual({
testFiles: [testFile],
configPath:
'x-pack/solutions/observability/plugins/my_plugin/test/scout/api/playwright.config.ts',
});
});
});

describe('Multiple files validation', () => {
it('should handle multiple files from same directory', () => {
const testFiles = [
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests/test1.spec.ts',
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests/test2.spec.ts',
];
const testFilesString = testFiles.join(',');

const result = validateAndProcessTestFiles(testFilesString);

expect(result).toEqual({
testFiles,
configPath:
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/playwright.config.ts',
});
});

it('should throw error for files from different directories', () => {
const testFilesString =
'path/scout/ui/tests/test1.spec.ts,path/scout/api/tests/test2.spec.ts';

expect(() => validateAndProcessTestFiles(testFilesString)).toThrow(
`All paths must be from the same scout test directory`
);
});
});

describe('Directory tests', () => {
beforeEach(() => {
// Mock for directory
mockFs.statSync.mockReturnValue({ isFile: () => false, isDirectory: () => true } as any);
});

it("should handle root 'test' directory with test files", () => {
const testDir = 'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests';

// Mock directory reading to return test files
mockFs.readdirSync.mockReturnValue([
{ name: 'test1.spec.ts', isDirectory: () => false, isFile: () => true },
{ name: 'test2.spec.ts', isDirectory: () => false, isFile: () => true },
] as any);

const result = validateAndProcessTestFiles(testDir);

expect(result).toEqual({
testFiles: [testDir],
configPath:
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/playwright.config.ts',
});
});

it('should handle sub-directory with test files', () => {
const testDir = 'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests/group1';

mockFs.readdirSync.mockImplementation((dirPath: any) => {
const pathStr = dirPath.toString();

if (pathStr.endsWith('group1')) {
// Mock the sub-directory containing test file
return [{ name: 'test1.spec.ts', isDirectory: () => false, isFile: () => true }] as any;
}
return [] as any;
});

const result = validateAndProcessTestFiles(testDir);

expect(result).toEqual({
testFiles: [testDir],
configPath:
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/playwright.config.ts',
});
});

it('should throw error for directory with no test files', () => {
const testDir = 'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests';

// Mock directory reading to return no test files
mockFs.readdirSync.mockReturnValue([
{ name: 'data.json', isDirectory: () => false, isFile: () => true },
] as any);

expect(() => validateAndProcessTestFiles(testDir)).toThrow(
`No test files found in directory: ${testDir}`
);
});
});

describe('Error cases', () => {
it('should throw error for invalid scout path', () => {
const testFile = 'some/other/path/test.spec.ts';

expect(() => validateAndProcessTestFiles(testFile)).toThrow(
`Test file must be within one of /scout/ui/tests, /scout/ui/parallel_tests, /scout/api/tests directories`
);
});

it('should throw error for non-existent file', () => {
mockFs.existsSync.mockReturnValue(false);
const testFile =
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests/test.spec.ts';

expect(() => validateAndProcessTestFiles(testFile)).toThrow(
`Path does not exist: ${testFile}`
);
});

it('should throw error for invalid path type', () => {
mockFs.statSync.mockReturnValue({ isFile: () => false, isDirectory: () => false } as any);
const testFile =
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests/symlink';

expect(() => validateAndProcessTestFiles(testFile)).toThrow(
`Path must be a file or directory: ${testFile}`
);
});

it('should throw error for non-test file', () => {
const testFile =
'x-pack/solutions/observability/plugins/my_plugin/test/scout/ui/tests/config.js';

expect(() => validateAndProcessTestFiles(testFile)).toThrow(
`File must be a test file ending '*.spec.ts': ${testFile}`
);
});
});
});
Loading