From ae2e0766f9bc109a9200c75d055e095f7324cc4b Mon Sep 17 00:00:00 2001 From: Yara Tercero Date: Mon, 3 May 2021 13:34:16 -0700 Subject: [PATCH] [Security Solution][Timeline] - update flakey timeline cypress tests (#99097) ###Summary Removes some anti-patterns I introduced in my last PR and makes use of Cypress' chainable pattern that will hopefully help with the flakiness. --- .../integration/timelines/notes_tab.spec.ts | 22 ++++++++---- .../timelines/open_timeline.spec.ts | 7 ++-- .../cypress/tasks/timeline.ts | 35 ++++++++++++------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts index 72f82aa54b7d5..fdc003039afbc 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/notes_tab.spec.ts @@ -26,12 +26,22 @@ describe('Timeline notes tab', () => { loginAndWaitForPageWithoutDateRange(TIMELINES_URL); waitForTimelinesPanelToBeLoaded(); - createTimeline(timeline).then((response) => { - timelineId = response.body.data.persistTimeline.timeline.savedObjectId; - waitForTimelinesPanelToBeLoaded(); - openTimelineById(timelineId!); - addNotesToTimeline(timeline.notes); - }); + createTimeline(timeline) + .then((response) => { + if (response.body.data.persistTimeline.timeline.savedObjectId == null) { + cy.log('"createTimeline" did not return expected response'); + } + timelineId = response.body.data.persistTimeline.timeline.savedObjectId; + waitForTimelinesPanelToBeLoaded(); + }) + .then(() => { + // TODO: It would be great to add response validation to avoid such things like + // the bang below and to more easily understand where failures are coming from - + // client vs server side + openTimelineById(timelineId!).then(() => { + addNotesToTimeline(timeline.notes); + }); + }); }); after(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts index 93c8d7265478e..7bf0409d91039 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/open_timeline.spec.ts @@ -48,9 +48,10 @@ describe('Open timeline', () => { addNoteToTimeline(note, timelineId!).should((response) => { expect(response.status).to.equal(200); waitForTimelinesPanelToBeLoaded(); - openTimelineById(timelineId!); - pinFirstEvent(); - markAsFavorite(); + openTimelineById(timelineId!).then(() => { + pinFirstEvent(); + markAsFavorite(); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 1474098b76281..a7842a7d98e83 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -89,13 +89,14 @@ export const addNameAndDescriptionToTimeline = (timeline: Timeline) => { cy.get(TIMELINE_TITLE_INPUT).should('not.exist'); }; -export const goToNotesTab = () => { +export const goToNotesTab = (): Cypress.Chainable> => { cy.root() .pipe(($el) => { $el.find(NOTES_TAB_BUTTON).trigger('click'); return $el.find(NOTES_TEXT_AREA); }) .should('be.visible'); + return cy.root().find(NOTES_TAB_BUTTON); }; export const getNotePreviewByNoteId = (noteId: string) => { @@ -112,15 +113,16 @@ export const goToQueryTab = () => { }; export const addNotesToTimeline = (notes: string) => { - cy.wait(150); - goToNotesTab(); - cy.get(NOTES_TEXT_AREA).type(notes); - cy.root() - .pipe(($el) => { - $el.find(ADD_NOTE_BUTTON).trigger('click'); - return $el.find(NOTES_TAB_BUTTON).find('.euiBadge'); - }) - .should('have.text', '1'); + goToNotesTab().then(() => { + cy.get(NOTES_TEXT_AREA).type(notes); + cy.root() + .pipe(($el) => { + $el.find(ADD_NOTE_BUTTON).trigger('click'); + return $el.find(NOTES_TAB_BUTTON).find('.euiBadge'); + }) + .should('have.text', '1'); + }); + goToQueryTab(); goToNotesTab(); }; @@ -136,7 +138,7 @@ export const addFilter = (filter: TimelineFilter) => { cy.get(SAVE_FILTER_BTN).click(); }; -export const addDataProvider = (filter: TimelineFilter) => { +export const addDataProvider = (filter: TimelineFilter): Cypress.Chainable> => { cy.get(TIMELINE_ADD_FIELD_BUTTON).click(); cy.get(TIMELINE_DATA_PROVIDER_FIELD).type(`${filter.field}{downarrow}{enter}`); cy.get(TIMELINE_DATA_PROVIDER_OPERATOR).type(filter.operator); @@ -231,13 +233,22 @@ export const openTimelineTemplateFromSettings = (id: string) => { cy.get(TIMELINE_TITLE_BY_ID(id)).click({ force: true }); }; -export const openTimelineById = (timelineId: string) => { +export const openTimelineById = (timelineId: string): Cypress.Chainable> => { + // Why are we checking for null if it is typed to 'string'? We don't currently validate the timeline response + // so technically we cannot guarantee that we will have the id. Changing the type to 'string | null' results in + // a lot of other changes being needed that would be best as part of a cleanup. Added a log, to give a dev a clue + // as to whether it's failing client or server side. + if (timelineId == null) { + cy.log('"timelineId" is null or undefined'); + } + cy.root() .pipe(($el) => { $el.find(TIMELINE_TITLE_BY_ID(timelineId)).trigger('click'); return $el.find(QUERY_TAB_BUTTON).find('.euiBadge'); }) .should('be.visible'); + return cy.root().find(TIMELINE_TITLE_BY_ID(timelineId)); }; export const pinFirstEvent = () => {