Skip to content

Commit edf5f81

Browse files
authored
test: Stabilize tests (#4398)
* test: stabilize a-run-before test * fix: localstorage not persisting after kubeconfig id login * test: stabilize custom-resources test * test: stabilize z-run-after test * test: stabilize acc tests * test: stabilize z-run-after test * test: stabilize chooseComboboxOption helper * test: stabilize custom-resources test * test: stabilize typeInSearch helper * test: stabilize typeInSearch helper * fix: retry transient streamin API errors * test: stabilize inspectTab helper * test: stabilize other helpers as well * test: stabilize acc-custom-resources * test: stabilize acc tests * test: stabilize community modules test * test: stabilize jobs test * test: stabilize jobs test * test: stabilize z run after test * fix: go network errors regex * fix: compile regex
1 parent 238dd04 commit edf5f81

21 files changed

+156
-47
lines changed

src/shared/hooks/BackendAPI/useGet.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import { authDataAtom } from '../../../state/authDataAtom';
1111
// allow <n> consecutive requests to fail before displaying error
1212
const ERROR_TOLERANCY = 2;
1313

14+
// Regex to detect Kubernetes API errors returned as stream content
15+
// These have the format: Get "https://...". EOF or similar
16+
const K8S_STREAM_ERROR_REGEX =
17+
/Get "https?:\/\/[^"]+"[.:]\s*(EOF|connection refused|i\/o timeout)/i;
18+
const MAX_LINE_LENGTH_TO_CHECK = 1000;
19+
1420
const useGetHook = (processDataFn) =>
1521
function (
1622
path,
@@ -187,15 +193,42 @@ export const useGetStream = (path) => {
187193
const fetch = useFetch();
188194
const readerRef = useRef(null);
189195
const abortController = useRef(new AbortController());
196+
const k8sErrorRetryCount = useRef(0);
190197

191198
const processError = (error) => {
192199
console.error(error);
193200
setError(error);
194201
};
195202

203+
// Unified retry logic for both text-based and exception-based errors
204+
const handleTransientError = (e) => {
205+
if (k8sErrorRetryCount.current < 5) {
206+
console.warn(`Transient stream error detected (${e}), retrying...`);
207+
k8sErrorRetryCount.current++;
208+
209+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
210+
timeoutRef.current = setTimeout(refetchData, 1000);
211+
} else {
212+
processError(e);
213+
}
214+
};
215+
196216
const processData = (data) => {
197217
const string = new TextDecoder().decode(data);
198218
const streams = string?.split('\n').filter((stream) => stream !== '');
219+
220+
const hasK8sError = streams.some((line) => {
221+
if (line.length > MAX_LINE_LENGTH_TO_CHECK) return false;
222+
223+
return K8S_STREAM_ERROR_REGEX.test(line);
224+
});
225+
226+
if (hasK8sError) {
227+
handleTransientError(new Error('Stream contained Kubernetes API Error'));
228+
return;
229+
}
230+
231+
k8sErrorRetryCount.current = 0;
199232
setData((prevData) => [...prevData, ...streams]);
200233
};
201234

@@ -235,14 +268,16 @@ export const useGetStream = (path) => {
235268

236269
return push();
237270
} catch (e) {
238-
processError(e);
271+
// Handle stream read errors (network-level)
272+
handleTransientError(e);
239273
}
240274
};
241275
push();
242276
},
243277
});
244278
} catch (e) {
245-
if (!e.toString().includes('abort')) processError(e);
279+
// Handle connection errors (network-level)
280+
if (!e.toString().includes('abort')) handleTransientError(e);
246281
}
247282
};
248283

src/state/clustersAtom.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,30 @@ export type ClustersState = {
99
[clusterName: string]: Cluster;
1010
} | null;
1111

12-
export const clustersAtom = atom<ClustersState>({});
13-
clustersAtom.debugLabel = 'clustersAtom';
14-
1512
const inMemoryClusters: ClustersState = {};
1613

14+
const getInitialClustersState = (): ClustersState => {
15+
try {
16+
const localStorageClusters = JSON.parse(
17+
localStorage.getItem(CLUSTERS_STORAGE_KEY) || '{}',
18+
);
19+
const sessionStorageClusters = JSON.parse(
20+
sessionStorage.getItem(CLUSTERS_STORAGE_KEY) || '{}',
21+
);
22+
return {
23+
...localStorageClusters,
24+
...sessionStorageClusters,
25+
...inMemoryClusters,
26+
};
27+
} catch (e) {
28+
console.warn('Cannot get clusters', e);
29+
return {};
30+
}
31+
};
32+
33+
export const clustersAtom = atom<ClustersState>(getInitialClustersState());
34+
clustersAtom.debugLabel = 'clustersAtom';
35+
1736
export const clustersAtomEffectSetSelf = withAtomEffect(
1837
clustersAtom,
1938
(_get, set) => {

tests/integration/cypress.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module.exports = defineConfig({
3737
'tests/accessibility/test-acc-performance.spec.js',
3838
'tests/accessibility/test-acc-extensions.spec.js',
3939
'tests/accessibility/test-acc-pizzas.spec.js',
40-
'tests/accessibility/test-acc-custom-resoureces.spec.js',
40+
'tests/accessibility/test-acc-custom-resources.spec.js',
4141
'tests/cluster/test-download-a-kubeconfig.spec.js',
4242
'tests/cluster/test-edit-cluster.spec.js',
4343
'tests/cluster/test-cluster-overview.spec.js',

tests/integration/support/commands.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Cypress.Commands.add('clickGenericListLink', (resourceName) => {
5252
.find('ui5-table-cell')
5353
.contains('ui5-text', resourceName)
5454
.click();
55+
56+
cy.wait(500);
5557
});
5658

5759
Cypress.Commands.add('clickListLink', (resourceName) => {
@@ -129,6 +131,10 @@ Cypress.Commands.add('getLeftNav', () => {
129131
return cy.get('aside');
130132
});
131133

134+
Cypress.Commands.add('getStartColumn', () => {
135+
return cy.get('div[slot="startColumn"]');
136+
});
137+
132138
Cypress.Commands.add('getMidColumn', () => {
133139
return cy.get('div[slot="midColumn"]');
134140
});
@@ -334,9 +340,10 @@ Cypress.Commands.add('closeEndColumn', (checkIfNotExist = false) => {
334340
else cy.getEndColumn().should('not.be.visible');
335341
});
336342

337-
Cypress.Commands.add('typeInSearch', (searchPhrase, force) => {
343+
Cypress.Commands.add('typeInSearch', (searchPhrase, force = false) => {
344+
cy.wait(500);
338345
cy.get('ui5-input[id^=search-]:visible')
339346
.find('input')
340-
.wait(1000)
347+
.should('be.visible')
341348
.type(searchPhrase, { force });
342349
});

tests/integration/support/create-edit-support.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Cypress.Commands.add('openCreate', () => {
22
cy.get('ui5-panel').contains('ui5-button', 'Create').click();
3+
cy.wait(500);
34
});
45

56
Cypress.Commands.add('saveChanges', (action = 'Create') => {

tests/integration/support/helpers.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ export function chooseComboboxOption(selector, optionText, force = false) {
22
cy.get(`ui5-combobox${selector}`)
33
.find('input')
44
.filterWithNoValue()
5-
.as('comboboxInput')
6-
.click();
7-
cy.get('@comboboxInput').type(optionText);
5+
.click()
6+
.wait(500)
7+
.type(optionText)
8+
.wait(500);
89

910
cy.get('ui5-cb-item:visible').contains(optionText).click({ force: force });
1011

tests/integration/support/inspectTab.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ Cypress.Commands.add(
22
'inspectTab',
33
{ prevSubject: ['optional', 'element'] },
44
(subject, tabName) => {
5-
(subject
6-
? cy.wrap(subject).find('ui5-tabcontainer')
7-
: cy.get('ui5-tabcontainer')
5+
return (
6+
subject
7+
? cy.wrap(subject).find('ui5-tabcontainer')
8+
: cy.get('ui5-tabcontainer')
89
)
910
.find('[role="tablist"]')
1011
.find('[role="tab"]')
1112
.contains(tabName)
1213
.should('be.visible')
13-
.as('tabButton');
14-
15-
return cy.get('@tabButton').click();
14+
.click();
1615
},
1716
);

tests/integration/support/navigate-to.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@ Cypress.Commands.add('navigateTo', (leftNav, resource) => {
22
cy.wait(1500)
33
.getLeftNav()
44
.get(`ui5-side-navigation-item[text="${leftNav}"]`)
5-
.as('btn-1')
6-
.should('be.visible');
7-
8-
cy.get('@btn-1').click();
5+
.should('be.visible')
6+
.click();
97

108
if (resource) {
119
cy.getLeftNav()
1210
.get(`ui5-side-navigation-sub-item[text="${resource}"]`)
13-
.as('btn-2')
14-
.should('be.visible');
15-
16-
cy.get('@btn-2').click();
11+
.should('be.visible')
12+
.click();
1713
}
1814
cy.wait(1500);
1915
});

tests/integration/tests/accessibility/test-acc-cron-jobs.spec.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ context('Accessibility test Cron Jobs', () => {
1515
cy.createNamespace(NAMESPACE_NAME);
1616
});
1717

18+
beforeEach(() => {
19+
cy.wait(500);
20+
});
21+
1822
it('Acc test Cron Jobs list', () => {
1923
cy.loginAndSelectCluster();
2024

tests/integration/tests/accessibility/test-acc-custom-resoureces.spec.js renamed to tests/integration/tests/accessibility/test-acc-custom-resources.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ context('Accessibility test Custom Resources', () => {
2727
cy.saveChanges('Create');
2828
});
2929

30+
beforeEach(() => {
31+
cy.wait(500);
32+
});
33+
3034
it('Acc test Custom Resources overview', () => {
3135
cy.getLeftNav().contains('Custom Resources').click();
3236

@@ -70,7 +74,7 @@ context('Accessibility test Custom Resources', () => {
7074

7175
cy.clickGenericListLink('Tclusters');
7276

73-
cy.contains('ui5-button', 'Create').click();
77+
cy.openCreate();
7478

7579
cy.wrap(loadFile(TCLUSTER_FILE_NAME)).then((TC_CONFIG) => {
7680
const TC = JSON.stringify(TC_CONFIG);

0 commit comments

Comments
 (0)