Skip to content

Commit dc8449f

Browse files
authored
Merge branch 'main' into rk/ab-font-sizes
2 parents 708c54f + c31bc22 commit dc8449f

937 files changed

Lines changed: 32986 additions & 13478 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/flaky-test-investigator/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ For every failure, try to retrieve:
4545

4646
Things to specifically check in the artifacts before forming a root-cause hypothesis:
4747

48-
- **Did the expected element render at all?** If yes and the selector missed it → flaky selector (Tier 2 fix territory). If no → real rendering / race / data issue (Tier 1 territory).
48+
- **Did the expected element render at all?** If yes and the selector missed it → flaky selector (Tier 2 fix territory). If no, distinguish **not yet** (a missing test-side wait) from **never** (the awaited state is unreachable — e.g. the component doesn't re-render when its async data arrives): read the component that renders the element, or find trace evidence of it appearing later. A missing wait in the test does not by itself prove the element would eventually have rendered.
4949
- **Is there an error visible in the UI** (toast, banner, console error in the HTML report)? If yes → product side, not test side.
5050
- **Is the page in an unexpected state** (different URL, different user's data, different space)? → cleanup or isolation issue, often points at `afterEach` / `afterAll`.
5151
- **Does the screenshot timestamp match the failure timestamp**? Stale artifacts from a prior step can mislead.
@@ -114,6 +114,7 @@ Watch out for these pitfalls when investigating the failure:
114114
- **Reducing coverage surface**: don't recommend stripping tags to skip the test in certain environments (e.g. Cloud) or project types (e.g. serverless Security) unless you have a real reason it shouldn't run there. "It's flaky here" is not a real reason.
115115
- **Trusting flaky-test-runner alone**: a green 30/30 or 60/60 run does not prove a fix held. The runner runs tests in isolation, which isn't always the case (Scout test runs share the same test servers for multiple test configs).
116116
- **Assuming "fix the test, not the product"**: always ask first whether the product could be at fault. Test-only fixes are meaningfully less durable than fixes that change production code.
117+
- **Reading fault from the throwing stack frame**: a waiting-side timeout always throws from the waiter (FTR/Playwright service code), so the frame tells you who threw, not whose fault it is. It is not evidence against a product bug.
117118
- **Reporting false certainty**: "I don't know, here are the two plausible explanations and what would distinguish them" is more useful to the owning team than a confident wrong answer.
118119

119120
### Is a fix worth it?

.buildkite/pipeline-utils/affected-packages/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ const filteredFiles = filterFilesByPackages(
134134

135135
On pull request builds, Jest unit and integration test groups are narrowed to configs under affected packages (see `pick_test_group_run_order` in CI stats). Add the GitHub label `ci:prevent-selective-testing` to run the full Jest suite instead. Touching files listed in `CRITICAL_FILES_JEST_*` in `const.ts` also skips filtering for the relevant test type.
136136

137+
### Always-run integration configs
138+
139+
Some integration suites boot a full Kibana and snapshot a *global registry* (rule-type params, connector types, task types, …) populated at runtime by downstream publishers that sit **upstream** of the suite's own package. `includeDownstream` expansion never reaches them, so a publisher-only change can silently skip the snapshot. Configs listed in `ALWAYS_RUN_JEST_INTEGRATION_CONFIGS` (`const.ts`) are re-added after affected-filtering so they run on every PR regardless of the graph. Keep the list tiny — it is a deliberate escape hatch.
140+
137141
## Scout selective testing: git -> Moon (shadow mode)
138142

139143
`resolve_selective_testing.ts` still uses **git** as the authoritative strategy (written to `.scout/code_changes.json`, no behavior change). In parallel, it runs the **Moon** strategy above for observation only, and writes the result plus a diff of `affectedModules` to `.scout/code_changes.moon_shadow.json` (uploaded as a Buildkite artifact). Mismatches are logged as a warning via `ToolingLog`; a Moon failure is swallowed and logged, never fails the build.

.buildkite/pipeline-utils/affected-packages/const.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,12 @@ export const CRITICAL_FILES_JEST_INTEGRATION_TESTS = [
4747
'.buildkite/pipeline-utils/affected-packages/**/*.{ts,js,sh}',
4848
'.buildkite/pipeline-utils/ci-stats/**/*.{ts,js}',
4949
];
50+
51+
// Integration configs that snapshot a global registry (rule-type params, connector types, task
52+
// types) fed by downstream plugins. Those publishers sit upstream of these configs, so
53+
// includeDownstream never marks them affected — they must run regardless of the graph. Keep tiny.
54+
export const ALWAYS_RUN_JEST_INTEGRATION_CONFIGS = [
55+
'x-pack/platform/plugins/shared/alerting/jest.integration.config.js',
56+
'x-pack/platform/plugins/shared/actions/jest.integration.config.js',
57+
'x-pack/platform/plugins/shared/task_manager/jest.integration.config.js',
58+
];
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
jest.mock('../../affected-packages', () => ({
11+
ALWAYS_RUN_JEST_INTEGRATION_CONFIGS: ['always/jest.integration.config.js'],
12+
CRITICAL_FILES_JEST_INTEGRATION_TESTS: ['CRITICAL_INT'],
13+
CRITICAL_FILES_JEST_UNIT_TESTS: ['CRITICAL_UNIT'],
14+
getAffectedPackages: jest.fn(),
15+
listChangedFiles: jest.fn(),
16+
filterFilesByPackages: (files: string[], pkgs: Set<string>) =>
17+
files.filter((f) => [...pkgs].some((pkg) => f.startsWith(pkg))),
18+
touchedCriticalFiles: (files: string[], critical: string[]) =>
19+
files.some((f) => critical.includes(f)),
20+
}));
21+
22+
jest.mock('./jest_configs', () => ({ SHARD_ANNOTATION_SEP: '||shard=' }));
23+
24+
import type { SelectiveTestingContext } from './selective_testing';
25+
import {
26+
filterJestIntegrationConfigsByAffected,
27+
filterJestUnitConfigsByAffected,
28+
} from './selective_testing';
29+
30+
const context = (
31+
affected: string[],
32+
changed: string[] = ['irrelevant.ts']
33+
): SelectiveTestingContext => ({
34+
affectedPackages: new Set(affected),
35+
prChangedFiles: changed,
36+
});
37+
38+
describe('filterJestIntegrationConfigsByAffected', () => {
39+
it('drops unaffected configs but re-adds always-run configs', () => {
40+
const configs = [
41+
'always/jest.integration.config.js',
42+
'other/jest.integration.config.js',
43+
'affected/jest.integration.config.js',
44+
];
45+
46+
const result = filterJestIntegrationConfigsByAffected(configs, context(['affected/']));
47+
48+
expect(result).toEqual(
49+
expect.arrayContaining([
50+
'always/jest.integration.config.js',
51+
'affected/jest.integration.config.js',
52+
])
53+
);
54+
expect(result).not.toContain('other/jest.integration.config.js');
55+
});
56+
57+
it('re-adds an always-run config even when no package is affected', () => {
58+
const configs = ['always/jest.integration.config.js', 'other/jest.integration.config.js'];
59+
60+
const result = filterJestIntegrationConfigsByAffected(configs, context([]));
61+
62+
expect(result).toEqual(['always/jest.integration.config.js']);
63+
});
64+
65+
it('restores every shard of an always-run config', () => {
66+
const configs = [
67+
'always/jest.integration.config.js||shard=1/2',
68+
'always/jest.integration.config.js||shard=2/2',
69+
'other/jest.integration.config.js',
70+
];
71+
72+
const result = filterJestIntegrationConfigsByAffected(configs, context(['nothing/']));
73+
74+
expect(result).toEqual([
75+
'always/jest.integration.config.js||shard=1/2',
76+
'always/jest.integration.config.js||shard=2/2',
77+
]);
78+
});
79+
80+
it('returns all configs unchanged when a critical file changed', () => {
81+
const configs = ['other/jest.integration.config.js'];
82+
83+
const result = filterJestIntegrationConfigsByAffected(configs, context([], ['CRITICAL_INT']));
84+
85+
expect(result).toEqual(configs);
86+
});
87+
});
88+
89+
describe('filterJestUnitConfigsByAffected', () => {
90+
it('does not force always-run integration configs into unit runs', () => {
91+
const configs = ['always/jest.integration.config.js', 'affected/jest.config.js'];
92+
93+
const result = filterJestUnitConfigsByAffected(configs, context(['affected/']));
94+
95+
expect(result).toEqual(['affected/jest.config.js']);
96+
});
97+
});

.buildkite/pipeline-utils/ci-stats/pick_test_group_run_order/selective_testing.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import {
11+
ALWAYS_RUN_JEST_INTEGRATION_CONFIGS,
1112
CRITICAL_FILES_JEST_INTEGRATION_TESTS,
1213
CRITICAL_FILES_JEST_UNIT_TESTS,
1314
filterFilesByPackages,
@@ -17,6 +18,7 @@ import {
1718
} from '../../affected-packages';
1819

1920
import { expandJestImplicitConsumers } from './jest_implicit_consumers';
21+
import { SHARD_ANNOTATION_SEP } from './jest_configs';
2022

2123
/**
2224
* The shared inputs both per-variant filters need: which packages the PR
@@ -73,7 +75,7 @@ export function filterJestUnitConfigsByAffected(
7375
});
7476
}
7577

76-
/** Narrow Jest integration configs to those owned by affected packages, unless a critical file changed. */
78+
/** Like the unit filter, but always re-adds ALWAYS_RUN_JEST_INTEGRATION_CONFIGS. */
7779
export function filterJestIntegrationConfigsByAffected(
7880
jestIntegrationConfigs: string[],
7981
context: SelectiveTestingContext
@@ -82,6 +84,7 @@ export function filterJestIntegrationConfigsByAffected(
8284
label: 'integration',
8385
configs: jestIntegrationConfigs,
8486
criticalFiles: CRITICAL_FILES_JEST_INTEGRATION_TESTS,
87+
alwaysRun: ALWAYS_RUN_JEST_INTEGRATION_CONFIGS,
8588
context,
8689
});
8790
}
@@ -90,16 +93,44 @@ function filterByAffected(args: {
9093
label: 'unit' | 'integration';
9194
configs: string[];
9295
criticalFiles: string[];
96+
alwaysRun?: readonly string[];
9397
context: SelectiveTestingContext;
9498
}): string[] {
95-
const { label, configs, criticalFiles, context } = args;
99+
const { label, configs, criticalFiles, alwaysRun = [], context } = args;
96100

97101
if (touchedCriticalFiles(context.prChangedFiles, criticalFiles)) {
98102
console.log(`Not filtering Jest ${label} tests because critical files changed`);
99103
return configs;
100104
}
101105

102106
const filtered = filterFilesByPackages(configs, context.affectedPackages);
103-
console.log(`Filtering Jest ${label} tests: ${configs.length} -> ${filtered.length}`);
104-
return filtered;
107+
const withAlwaysRun = addAlwaysRunConfigs(filtered, configs, alwaysRun);
108+
console.log(`Filtering Jest ${label} tests: ${configs.length} -> ${withAlwaysRun.length}`);
109+
return withAlwaysRun;
110+
}
111+
112+
// Matches on the base path so every shard of an always-run config is restored.
113+
function addAlwaysRunConfigs(
114+
filtered: string[],
115+
allConfigs: string[],
116+
alwaysRun: readonly string[]
117+
): string[] {
118+
if (alwaysRun.length === 0) {
119+
return filtered;
120+
}
121+
122+
const alwaysRunSet = new Set(alwaysRun);
123+
const result = new Set(filtered);
124+
for (const config of allConfigs) {
125+
if (alwaysRunSet.has(baseConfigPath(config)) && !result.has(config)) {
126+
result.add(config);
127+
console.log(`Always-run Jest integration config re-added: ${config}`);
128+
}
129+
}
130+
return [...result];
131+
}
132+
133+
function baseConfigPath(config: string): string {
134+
const idx = config.indexOf(SHARD_ANNOTATION_SEP);
135+
return idx === -1 ? config : config.slice(0, idx);
105136
}

.buildkite/scripts/steps/security/third_party_packages.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
eslint-plugin-n
12
@xterm/xterm
23
knip
34
safe-stable-stringify
@@ -58,4 +59,5 @@ redux-thunk-v2
5859
redux-toolkit-v1
5960
redux-v4
6061
reselect-v4
62+
@swc/plugin-emotion
6163
graphql

.eslintrc.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,7 @@ module.exports = {
15231523
'x-pack/solutions/security/packages/data-stream-adapter/**/*.{js,mjs,ts,tsx}',
15241524
'src/platform/packages/shared/kbn-cell-actions/**/*.{js,mjs,ts,tsx}',
15251525
],
1526-
plugins: ['eslint-plugin-node', 'react'],
1526+
plugins: ['eslint-plugin-n', 'react'],
15271527
env: {
15281528
jest: true,
15291529
},
@@ -1532,7 +1532,7 @@ module.exports = {
15321532
'array-callback-return': 'error',
15331533
'no-array-constructor': 'error',
15341534
complexity: 'warn',
1535-
'node/no-deprecated-api': 'error',
1535+
'n/no-deprecated-api': 'error',
15361536
'no-bitwise': 'error',
15371537
'no-continue': 'error',
15381538
'no-dupe-keys': 'error',
@@ -1824,7 +1824,7 @@ module.exports = {
18241824
{
18251825
// typescript and javascript for front and back
18261826
files: ['x-pack/solutions/security/plugins/lists/**/*.{js,mjs,ts,tsx}'],
1827-
plugins: ['eslint-plugin-node'],
1827+
plugins: ['eslint-plugin-n'],
18281828
env: {
18291829
jest: true,
18301830
},
@@ -1848,7 +1848,7 @@ module.exports = {
18481848
ignoreDeclarationSort: true,
18491849
},
18501850
],
1851-
'node/no-deprecated-api': 'error',
1851+
'n/no-deprecated-api': 'error',
18521852
'no-bitwise': 'error',
18531853
'no-continue': 'error',
18541854
'no-dupe-keys': 'error',
@@ -2540,7 +2540,7 @@ module.exports = {
25402540
'src/platform/packages/shared/kbn-workflows/**/*.{js,mjs,ts,tsx}',
25412541
'src/platform/packages/shared/kbn-workflows-ui/**/*.{js,mjs,ts,tsx}',
25422542
],
2543-
plugins: ['eslint-plugin-node', 'react'],
2543+
plugins: ['eslint-plugin-n', 'react'],
25442544
env: {
25452545
jest: true,
25462546
},
@@ -2549,7 +2549,7 @@ module.exports = {
25492549
'array-callback-return': 'error',
25502550
'no-array-constructor': 'error',
25512551
complexity: 'warn',
2552-
'node/no-deprecated-api': 'error',
2552+
'n/no-deprecated-api': 'error',
25532553
'no-bitwise': 'error',
25542554
'no-continue': 'error',
25552555
'no-dupe-keys': 'error',

0 commit comments

Comments
 (0)