[8.x] [scout] adding test helper @kbn/scout-oblt package and uptate onboarding tests (#209761) (#211490)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[scout] adding test helper `@kbn/scout-oblt` package and
uptate onboarding tests
(#209761)](https://github.com/elastic/kibana/pull/209761)

<!--- Backport version: 9.6.4 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Dzmitry
Lemechko","email":"dzmitry.lemechko@elastic.co"},"sourceCommit":{"committedDate":"2025-02-11T17:38:41Z","message":"[scout]
adding test helper `@kbn/scout-oblt` package and uptate onboarding tests
(#209761)\n\n## Summary\r\n\r\n`@kbn/scout-oblt` is a test library that
extends `@kbn/scout` with test\r\nhelpers specifically designed to test
`Observability` applications in\r\nKibana. All Oblt plugins should only
import from `@kbn/scout-oblt`\r\n\r\nIts primary goal is to simplify the
test development experience for\r\nteams working on `Observability`
plugins by providing custom Playwright\r\nfixtures, page objects, and
utilities tailored for Observability-related\r\ntesting
scenarios.\r\n\r\nContributing:\r\n- when Fixture/Page Object is
sharable across all Solutions and Platform\r\n(`fleetApi` fixture), it
should be added in `@kbn/scout`\r\n- when Fixture/Page Object is
Oblt-specific but is shared across tests\r\nunder the multiple plugins
(`OnboardingHome` page), it should be added\r\nin `@kbn/scout-oblt`\r\n-
when Fixture/Page Object is only used in a single plugin
(`onboarding`\r\ninternal APIs ?), it should be added in this
plugin.\r\n\r\nI also re-worked existing tests with few ideas in
mind:\r\n- Scout is **e2e testing tool** and should target primary e2e
test\r\nscenarios; We have _API integration tests_ to test multiple
short\r\nscenarios for APIs behavior (response, status code) and
_jest/React\r\ntesting library_ to test components in isolation
(elements rendering,\r\nfields validation). Doing all the testing with
e2e tool like Playwright\r\nwill dramatically affect cost efficiency and
stability of tests, but\r\nalso slows overall CI execution and PRs
delivery. The goal is to follow\r\ntesting pyramid and keep in mind its
principles.\r\n- We on purpose spin up new browser context for each
`test` block to\r\nmake sure our **tests are independent**. Having too
many short `test`\r\nblocks in the file significantly slows down the
execution: every block\r\ntriggers browser context, saml authentication,
adding/removing Fleet\r\nintegrations (each call up to 2 seconds) and
other beforeEach/afterEach\r\nhooks. Real browser-based testing is
expensive. It is not about putting\r\nevery step into 1 `test` block,
but also not a Jest unit-test-style\r\ndesign. When it is possible to
group similar actions on the same page\r\nand if it is a part of the
same user flow - we should do it. It also\r\ndoesn't bring the testing
value repeating the same UI steps multiple\r\ntimes in different
scenarios. _Our CI costs are critical to cut when it\r\nis
possible_\r\n- Avoid **nesting describe** blocks: it complicates test
readability and\r\nalso complicates for CI bot to properly skip the
failing block (it will\r\nskip the top level one). We encourage **Scout
parallel test execution**\r\nbased on running test spec files in
multiple workers, not the `test`\r\nblocks within the same file. Having
too many `test` blocks in the same\r\nfile will be slowly run in the
single thread and in case of flakiness,\r\nit means Team lose more test
coverage than they probably expect.\r\n\r\nBefore (**59** test blocks -
**8-8.5 min** per distro):\r\n<img width=\"1709\" alt=\"Screenshot
2025-02-08 at 18 01
40\"\r\nsrc=\"https://github.com/user-attachments/assets/5fd65a1c-85f9-4594-9dae-3f8e99a005ab\"\r\n/>\r\n\r\nAfter
(**15** test blocks - **3.5-4 min** per distro):\r\n<img width=\"1578\"
alt=\"Screenshot 2025-02-10 at 18 14
42\"\r\nsrc=\"https://github.com/user-attachments/assets/6846898f-7dd2-4f6b-8bc5-d06741b0b120\"\r\n/>\r\n\r\nFor
reviewers: updated tests are possible to run in 2 parallel
workers\r\nagainst the same Kibana/ES instance and run time is dropping
to **2.5-3\r\nmin** 🚀 . It is up to UX-Logs team to decide if you want
to keep\r\nparallel run (new tests can be added either to parallel or
sequential\r\nrun)\r\n<img width=\"1578\" alt=\"Screenshot 2025-02-11 at
12 14
30\"\r\nsrc=\"https://github.com/user-attachments/assets/e94113f2-d7f1-470e-a6d5-cb5154d99c41\"\r\n/>\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"bd13e829498032c07bf8490f770a563f34e9f856","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:version","test:scout","v9.1.0","v8.19.0"],"title":"[scout]
adding test helper `@kbn/scout-oblt` package and uptate onboarding
tests","number":209761,"url":"https://github.com/elastic/kibana/pull/209761","mergeCommit":{"message":"[scout]
adding test helper `@kbn/scout-oblt` package and uptate onboarding tests
(#209761)\n\n## Summary\r\n\r\n`@kbn/scout-oblt` is a test library that
extends `@kbn/scout` with test\r\nhelpers specifically designed to test
`Observability` applications in\r\nKibana. All Oblt plugins should only
import from `@kbn/scout-oblt`\r\n\r\nIts primary goal is to simplify the
test development experience for\r\nteams working on `Observability`
plugins by providing custom Playwright\r\nfixtures, page objects, and
utilities tailored for Observability-related\r\ntesting
scenarios.\r\n\r\nContributing:\r\n- when Fixture/Page Object is
sharable across all Solutions and Platform\r\n(`fleetApi` fixture), it
should be added in `@kbn/scout`\r\n- when Fixture/Page Object is
Oblt-specific but is shared across tests\r\nunder the multiple plugins
(`OnboardingHome` page), it should be added\r\nin `@kbn/scout-oblt`\r\n-
when Fixture/Page Object is only used in a single plugin
(`onboarding`\r\ninternal APIs ?), it should be added in this
plugin.\r\n\r\nI also re-worked existing tests with few ideas in
mind:\r\n- Scout is **e2e testing tool** and should target primary e2e
test\r\nscenarios; We have _API integration tests_ to test multiple
short\r\nscenarios for APIs behavior (response, status code) and
_jest/React\r\ntesting library_ to test components in isolation
(elements rendering,\r\nfields validation). Doing all the testing with
e2e tool like Playwright\r\nwill dramatically affect cost efficiency and
stability of tests, but\r\nalso slows overall CI execution and PRs
delivery. The goal is to follow\r\ntesting pyramid and keep in mind its
principles.\r\n- We on purpose spin up new browser context for each
`test` block to\r\nmake sure our **tests are independent**. Having too
many short `test`\r\nblocks in the file significantly slows down the
execution: every block\r\ntriggers browser context, saml authentication,
adding/removing Fleet\r\nintegrations (each call up to 2 seconds) and
other beforeEach/afterEach\r\nhooks. Real browser-based testing is
expensive. It is not about putting\r\nevery step into 1 `test` block,
but also not a Jest unit-test-style\r\ndesign. When it is possible to
group similar actions on the same page\r\nand if it is a part of the
same user flow - we should do it. It also\r\ndoesn't bring the testing
value repeating the same UI steps multiple\r\ntimes in different
scenarios. _Our CI costs are critical to cut when it\r\nis
possible_\r\n- Avoid **nesting describe** blocks: it complicates test
readability and\r\nalso complicates for CI bot to properly skip the
failing block (it will\r\nskip the top level one). We encourage **Scout
parallel test execution**\r\nbased on running test spec files in
multiple workers, not the `test`\r\nblocks within the same file. Having
too many `test` blocks in the same\r\nfile will be slowly run in the
single thread and in case of flakiness,\r\nit means Team lose more test
coverage than they probably expect.\r\n\r\nBefore (**59** test blocks -
**8-8.5 min** per distro):\r\n<img width=\"1709\" alt=\"Screenshot
2025-02-08 at 18 01
40\"\r\nsrc=\"https://github.com/user-attachments/assets/5fd65a1c-85f9-4594-9dae-3f8e99a005ab\"\r\n/>\r\n\r\nAfter
(**15** test blocks - **3.5-4 min** per distro):\r\n<img width=\"1578\"
alt=\"Screenshot 2025-02-10 at 18 14
42\"\r\nsrc=\"https://github.com/user-attachments/assets/6846898f-7dd2-4f6b-8bc5-d06741b0b120\"\r\n/>\r\n\r\nFor
reviewers: updated tests are possible to run in 2 parallel
workers\r\nagainst the same Kibana/ES instance and run time is dropping
to **2.5-3\r\nmin** 🚀 . It is up to UX-Logs team to decide if you want
to keep\r\nparallel run (new tests can be added either to parallel or
sequential\r\nrun)\r\n<img width=\"1578\" alt=\"Screenshot 2025-02-11 at
12 14
30\"\r\nsrc=\"https://github.com/user-attachments/assets/e94113f2-d7f1-470e-a6d5-cb5154d99c41\"\r\n/>\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"bd13e829498032c07bf8490f770a563f34e9f856"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/210675","number":210675,"state":"MERGED","mergeCommit":{"sha":"187b2307220a1f54776b0b3b05d131b0e75b6e03","message":"[9.0]
[scout] adding test helper &#x60;@kbn/scout-oblt&#x60; package and
uptate onboarding tests (#209761) (#210675)\n\n# Backport\n\nThis will
backport the following commits from `main` to `9.0`:\n- [[scout] adding
test helper &#x60;@kbn/scout-oblt&#x60; package and\nuptate onboarding
tests\n(#209761)](https://github.com/elastic/kibana/pull/209761)\n\n<!---
Backport version: 9.4.3 -->\n\n### Questions ?\nPlease refer to the
[Backport
tool\ndocumentation](https://github.com/sqren/backport)\n\n<!--BACKPORT
[{\"author\":{\"name\":\"Dzmitry\nLemechko\",\"email\":\"dzmitry.lemechko@elastic.co\"},\"sourceCommit\":{\"committedDate\":\"2025-02-11T17:38:41Z\",\"message\":\"[scout]\nadding
test helper `@kbn/scout-oblt` package and uptate onboarding
tests\n(#209761)\\n\\n## Summary\\r\\n\\r\\n`@kbn/scout-oblt` is a test
library that\nextends `@kbn/scout` with test\\r\\nhelpers specifically
designed to test\n`Observability` applications in\\r\\nKibana. All Oblt
plugins should only\nimport from `@kbn/scout-oblt`\\r\\n\\r\\nIts
primary goal is to simplify the\ntest development experience
for\\r\\nteams working on `Observability`\nplugins by providing custom
Playwright\\r\\nfixtures, page objects, and\nutilities tailored for
Observability-related\\r\\ntesting\nscenarios.\\r\\n\\r\\nContributing:\\r\\n-
when Fixture/Page Object is\nsharable across all Solutions and
Platform\\r\\n(`fleetApi` fixture), it\nshould be added in
`@kbn/scout`\\r\\n- when Fixture/Page Object is\nOblt-specific but is
shared across tests\\r\\nunder the multiple plugins\n(`OnboardingHome`
page), it should be added\\r\\nin `@kbn/scout-oblt`\\r\\n-\nwhen
Fixture/Page Object is only used in a single
plugin\n(`onboarding`\\r\\ninternal APIs ?), it should be added in
this\nplugin.\\r\\n\\r\\nI also re-worked existing tests with few ideas
in\nmind:\\r\\n- Scout is **e2e testing tool** and should target primary
e2e\ntest\\r\\nscenarios; We have _API integration tests_ to test
multiple\nshort\\r\\nscenarios for APIs behavior (response, status code)
and\n_jest/React\\r\\ntesting library_ to test components in
isolation\n(elements rendering,\\r\\nfields validation). Doing all the
testing with\ne2e tool like Playwright\\r\\nwill dramatically affect
cost efficiency and\nstability of tests, but\\r\\nalso slows overall CI
execution and PRs\ndelivery. The goal is to follow\\r\\ntesting pyramid
and keep in mind its\nprinciples.\\r\\n- We on purpose spin up new
browser context for each\n`test` block to\\r\\nmake sure our **tests are
independent**. Having too\nmany short `test`\\r\\nblocks in the file
significantly slows down the\nexecution: every block\\r\\ntriggers
browser context, saml authentication,\nadding/removing
Fleet\\r\\nintegrations (each call up to 2 seconds) and\nother
beforeEach/afterEach\\r\\nhooks. Real browser-based testing
is\nexpensive. It is not about putting\\r\\nevery step into 1 `test`
block,\nbut also not a Jest unit-test-style\\r\\ndesign. When it is
possible to\ngroup similar actions on the same page\\r\\nand if it is a
part of the\nsame user flow - we should do it. It also\\r\\ndoesn't
bring the testing\nvalue repeating the same UI steps multiple\\r\\ntimes
in different\nscenarios. _Our CI costs are critical to cut when
it\\r\\nis\npossible_\\r\\n- Avoid **nesting describe** blocks: it
complicates test\nreadability and\\r\\nalso complicates for CI bot to
properly skip the\nfailing block (it will\\r\\nskip the top level one).
We encourage **Scout\nparallel test execution**\\r\\nbased on running
test spec files in\nmultiple workers, not the `test`\\r\\nblocks within
the same file. Having\ntoo many `test` blocks in the same\\r\\nfile will
be slowly run in the\nsingle thread and in case of flakiness,\\r\\nit
means Team lose more test\ncoverage than they probably
expect.\\r\\n\\r\\nBefore (**59** test blocks -\n**8-8.5 min** per
distro):\\r\\n<img width=\\\"1709\\\" alt=\\\"Screenshot\n2025-02-08 at
18
01\n40\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/5fd65a1c-85f9-4594-9dae-3f8e99a005ab\\\"\\r\\n/>\\r\\n\\r\\nAfter\n(**15**
test blocks - **3.5-4 min** per distro):\\r\\n<img
width=\\\"1578\\\"\nalt=\\\"Screenshot 2025-02-10 at 18
14\n42\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/6846898f-7dd2-4f6b-8bc5-d06741b0b120\\\"\\r\\n/>\\r\\n\\r\\nFor\nreviewers:
updated tests are possible to run in 2 parallel\nworkers\\r\\nagainst
the same Kibana/ES instance and run time is dropping\nto
**2.5-3\\r\\nmin** 🚀 . It is up to UX-Logs team to decide if you
want\nto keep\\r\\nparallel run (new tests can be added either to
parallel or\nsequential\\r\\nrun)\\r\\n<img width=\\\"1578\\\"
alt=\\\"Screenshot 2025-02-11 at\n12
14\n30\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/e94113f2-d7f1-470e-a6d5-cb5154d99c41\\\"\\r\\n/>\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by:\nkibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\",\"sha\":\"bd13e829498032c07bf8490f770a563f34e9f856\",\"branchLabelMapping\":{\"^v9.1.0$\":\"main\",\"^v8.19.0$\":\"8.x\",\"^v(\\\\d+).(\\\\d+).\\\\d+$\":\"$1.$2\"}},\"sourcePullRequest\":{\"labels\":[\"release_note:skip\",\"v9.0.0\",\"backport:version\",\"test:scout\",\"v9.1.0\",\"v8.19.0\"],\"title\":\"[scout]\nadding
test helper `@kbn/scout-oblt` package and uptate
onboarding\ntests\",\"number\":209761,\"url\":\"https://github.com/elastic/kibana/pull/209761\",\"mergeCommit\":{\"message\":\"[scout]\nadding
test helper `@kbn/scout-oblt` package and uptate onboarding
tests\n(#209761)\\n\\n## Summary\\r\\n\\r\\n`@kbn/scout-oblt` is a test
library that\nextends `@kbn/scout` with test\\r\\nhelpers specifically
designed to test\n`Observability` applications in\\r\\nKibana. All Oblt
plugins should only\nimport from `@kbn/scout-oblt`\\r\\n\\r\\nIts
primary goal is to simplify the\ntest development experience
for\\r\\nteams working on `Observability`\nplugins by providing custom
Playwright\\r\\nfixtures, page objects, and\nutilities tailored for
Observability-related\\r\\ntesting\nscenarios.\\r\\n\\r\\nContributing:\\r\\n-
when Fixture/Page Object is\nsharable across all Solutions and
Platform\\r\\n(`fleetApi` fixture), it\nshould be added in
`@kbn/scout`\\r\\n- when Fixture/Page Object is\nOblt-specific but is
shared across tests\\r\\nunder the multiple plugins\n(`OnboardingHome`
page), it should be added\\r\\nin `@kbn/scout-oblt`\\r\\n-\nwhen
Fixture/Page Object is only used in a single
plugin\n(`onboarding`\\r\\ninternal APIs ?), it should be added in
this\nplugin.\\r\\n\\r\\nI also re-worked existing tests with few ideas
in\nmind:\\r\\n- Scout is **e2e testing tool** and should target primary
e2e\ntest\\r\\nscenarios; We have _API integration tests_ to test
multiple\nshort\\r\\nscenarios for APIs behavior (response, status code)
and\n_jest/React\\r\\ntesting library_ to test components in
isolation\n(elements rendering,\\r\\nfields validation). Doing all the
testing with\ne2e tool like Playwright\\r\\nwill dramatically affect
cost efficiency and\nstability of tests, but\\r\\nalso slows overall CI
execution and PRs\ndelivery. The goal is to follow\\r\\ntesting pyramid
and keep in mind its\nprinciples.\\r\\n- We on purpose spin up new
browser context for each\n`test` block to\\r\\nmake sure our **tests are
independent**. Having too\nmany short `test`\\r\\nblocks in the file
significantly slows down the\nexecution: every block\\r\\ntriggers
browser context, saml authentication,\nadding/removing
Fleet\\r\\nintegrations (each call up to 2 seconds) and\nother
beforeEach/afterEach\\r\\nhooks. Real browser-based testing
is\nexpensive. It is not about putting\\r\\nevery step into 1 `test`
block,\nbut also not a Jest unit-test-style\\r\\ndesign. When it is
possible to\ngroup similar actions on the same page\\r\\nand if it is a
part of the\nsame user flow - we should do it. It also\\r\\ndoesn't
bring the testing\nvalue repeating the same UI steps multiple\\r\\ntimes
in different\nscenarios. _Our CI costs are critical to cut when
it\\r\\nis\npossible_\\r\\n- Avoid **nesting describe** blocks: it
complicates test\nreadability and\\r\\nalso complicates for CI bot to
properly skip the\nfailing block (it will\\r\\nskip the top level one).
We encourage **Scout\nparallel test execution**\\r\\nbased on running
test spec files in\nmultiple workers, not the `test`\\r\\nblocks within
the same file. Having\ntoo many `test` blocks in the same\\r\\nfile will
be slowly run in the\nsingle thread and in case of flakiness,\\r\\nit
means Team lose more test\ncoverage than they probably
expect.\\r\\n\\r\\nBefore (**59** test blocks -\n**8-8.5 min** per
distro):\\r\\n<img width=\\\"1709\\\" alt=\\\"Screenshot\n2025-02-08 at
18
01\n40\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/5fd65a1c-85f9-4594-9dae-3f8e99a005ab\\\"\\r\\n/>\\r\\n\\r\\nAfter\n(**15**
test blocks - **3.5-4 min** per distro):\\r\\n<img
width=\\\"1578\\\"\nalt=\\\"Screenshot 2025-02-10 at 18
14\n42\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/6846898f-7dd2-4f6b-8bc5-d06741b0b120\\\"\\r\\n/>\\r\\n\\r\\nFor\nreviewers:
updated tests are possible to run in 2 parallel\nworkers\\r\\nagainst
the same Kibana/ES instance and run time is dropping\nto
**2.5-3\\r\\nmin** 🚀 . It is up to UX-Logs team to decide if you
want\nto keep\\r\\nparallel run (new tests can be added either to
parallel or\nsequential\\r\\nrun)\\r\\n<img width=\\\"1578\\\"
alt=\\\"Screenshot 2025-02-11 at\n12
14\n30\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/e94113f2-d7f1-470e-a6d5-cb5154d99c41\\\"\\r\\n/>\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by:\nkibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\",\"sha\":\"bd13e829498032c07bf8490f770a563f34e9f856\"}},\"sourceBranch\":\"main\",\"suggestedTargetBranches\":[\"9.0\",\"8.x\"],\"targetPullRequestStates\":[{\"branch\":\"9.0\",\"label\":\"v9.0.0\",\"branchLabelMappingKey\":\"^v(\\\\d+).(\\\\d+).\\\\d+$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"},{\"branch\":\"main\",\"label\":\"v9.1.0\",\"branchLabelMappingKey\":\"^v9.1.0$\",\"isSourceBranch\":true,\"state\":\"MERGED\",\"url\":\"https://github.com/elastic/kibana/pull/209761\",\"number\":209761,\"mergeCommit\":{\"message\":\"[scout]\nadding
test helper `@kbn/scout-oblt` package and uptate onboarding
tests\n(#209761)\\n\\n## Summary\\r\\n\\r\\n`@kbn/scout-oblt` is a test
library that\nextends `@kbn/scout` with test\\r\\nhelpers specifically
designed to test\n`Observability` applications in\\r\\nKibana. All Oblt
plugins should only\nimport from `@kbn/scout-oblt`\\r\\n\\r\\nIts
primary goal is to simplify the\ntest development experience
for\\r\\nteams working on `Observability`\nplugins by providing custom
Playwright\\r\\nfixtures, page objects, and\nutilities tailored for
Observability-related\\r\\ntesting\nscenarios.\\r\\n\\r\\nContributing:\\r\\n-
when Fixture/Page Object is\nsharable across all Solutions and
Platform\\r\\n(`fleetApi` fixture), it\nshould be added in
`@kbn/scout`\\r\\n- when Fixture/Page Object is\nOblt-specific but is
shared across tests\\r\\nunder the multiple plugins\n(`OnboardingHome`
page), it should be added\\r\\nin `@kbn/scout-oblt`\\r\\n-\nwhen
Fixture/Page Object is only used in a single
plugin\n(`onboarding`\\r\\ninternal APIs ?), it should be added in
this\nplugin.\\r\\n\\r\\nI also re-worked existing tests with few ideas
in\nmind:\\r\\n- Scout is **e2e testing tool** and should target primary
e2e\ntest\\r\\nscenarios; We have _API integration tests_ to test
multiple\nshort\\r\\nscenarios for APIs behavior (response, status code)
and\n_jest/React\\r\\ntesting library_ to test components in
isolation\n(elements rendering,\\r\\nfields validation). Doing all the
testing with\ne2e tool like Playwright\\r\\nwill dramatically affect
cost efficiency and\nstability of tests, but\\r\\nalso slows overall CI
execution and PRs\ndelivery. The goal is to follow\\r\\ntesting pyramid
and keep in mind its\nprinciples.\\r\\n- We on purpose spin up new
browser context for each\n`test` block to\\r\\nmake sure our **tests are
independent**. Having too\nmany short `test`\\r\\nblocks in the file
significantly slows down the\nexecution: every block\\r\\ntriggers
browser context, saml authentication,\nadding/removing
Fleet\\r\\nintegrations (each call up to 2 seconds) and\nother
beforeEach/afterEach\\r\\nhooks. Real browser-based testing
is\nexpensive. It is not about putting\\r\\nevery step into 1 `test`
block,\nbut also not a Jest unit-test-style\\r\\ndesign. When it is
possible to\ngroup similar actions on the same page\\r\\nand if it is a
part of the\nsame user flow - we should do it. It also\\r\\ndoesn't
bring the testing\nvalue repeating the same UI steps multiple\\r\\ntimes
in different\nscenarios. _Our CI costs are critical to cut when
it\\r\\nis\npossible_\\r\\n- Avoid **nesting describe** blocks: it
complicates test\nreadability and\\r\\nalso complicates for CI bot to
properly skip the\nfailing block (it will\\r\\nskip the top level one).
We encourage **Scout\nparallel test execution**\\r\\nbased on running
test spec files in\nmultiple workers, not the `test`\\r\\nblocks within
the same file. Having\ntoo many `test` blocks in the same\\r\\nfile will
be slowly run in the\nsingle thread and in case of flakiness,\\r\\nit
means Team lose more test\ncoverage than they probably
expect.\\r\\n\\r\\nBefore (**59** test blocks -\n**8-8.5 min** per
distro):\\r\\n<img width=\\\"1709\\\" alt=\\\"Screenshot\n2025-02-08 at
18
01\n40\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/5fd65a1c-85f9-4594-9dae-3f8e99a005ab\\\"\\r\\n/>\\r\\n\\r\\nAfter\n(**15**
test blocks - **3.5-4 min** per distro):\\r\\n<img
width=\\\"1578\\\"\nalt=\\\"Screenshot 2025-02-10 at 18
14\n42\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/6846898f-7dd2-4f6b-8bc5-d06741b0b120\\\"\\r\\n/>\\r\\n\\r\\nFor\nreviewers:
updated tests are possible to run in 2 parallel\nworkers\\r\\nagainst
the same Kibana/ES instance and run time is dropping\nto
**2.5-3\\r\\nmin** 🚀 . It is up to UX-Logs team to decide if you
want\nto keep\\r\\nparallel run (new tests can be added either to
parallel or\nsequential\\r\\nrun)\\r\\n<img width=\\\"1578\\\"
alt=\\\"Screenshot 2025-02-11 at\n12
14\n30\\\"\\r\\nsrc=\\\"https://github.com/user-attachments/assets/e94113f2-d7f1-470e-a6d5-cb5154d99c41\\\"\\r\\n/>\\r\\n\\r\\n---------\\r\\n\\r\\nCo-authored-by:\nkibanamachine\n<42973632+kibanamachine@users.noreply.github.com>\",\"sha\":\"bd13e829498032c07bf8490f770a563f34e9f856\"}},{\"branch\":\"8.x\",\"label\":\"v8.19.0\",\"branchLabelMappingKey\":\"^v8.19.0$\",\"isSourceBranch\":false,\"state\":\"NOT_CREATED\"}]}]\nBACKPORT-->\n\nCo-authored-by:
Dzmitry Lemechko
<dzmitry.lemechko@elastic.co>"}},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/209761","number":209761,"mergeCommit":{"message":"[scout]
adding test helper `@kbn/scout-oblt` package and uptate onboarding tests
(#209761)\n\n## Summary\r\n\r\n`@kbn/scout-oblt` is a test library that
extends `@kbn/scout` with test\r\nhelpers specifically designed to test
`Observability` applications in\r\nKibana. All Oblt plugins should only
import from `@kbn/scout-oblt`\r\n\r\nIts primary goal is to simplify the
test development experience for\r\nteams working on `Observability`
plugins by providing custom Playwright\r\nfixtures, page objects, and
utilities tailored for Observability-related\r\ntesting
scenarios.\r\n\r\nContributing:\r\n- when Fixture/Page Object is
sharable across all Solutions and Platform\r\n(`fleetApi` fixture), it
should be added in `@kbn/scout`\r\n- when Fixture/Page Object is
Oblt-specific but is shared across tests\r\nunder the multiple plugins
(`OnboardingHome` page), it should be added\r\nin `@kbn/scout-oblt`\r\n-
when Fixture/Page Object is only used in a single plugin
(`onboarding`\r\ninternal APIs ?), it should be added in this
plugin.\r\n\r\nI also re-worked existing tests with few ideas in
mind:\r\n- Scout is **e2e testing tool** and should target primary e2e
test\r\nscenarios; We have _API integration tests_ to test multiple
short\r\nscenarios for APIs behavior (response, status code) and
_jest/React\r\ntesting library_ to test components in isolation
(elements rendering,\r\nfields validation). Doing all the testing with
e2e tool like Playwright\r\nwill dramatically affect cost efficiency and
stability of tests, but\r\nalso slows overall CI execution and PRs
delivery. The goal is to follow\r\ntesting pyramid and keep in mind its
principles.\r\n- We on purpose spin up new browser context for each
`test` block to\r\nmake sure our **tests are independent**. Having too
many short `test`\r\nblocks in the file significantly slows down the
execution: every block\r\ntriggers browser context, saml authentication,
adding/removing Fleet\r\nintegrations (each call up to 2 seconds) and
other beforeEach/afterEach\r\nhooks. Real browser-based testing is
expensive. It is not about putting\r\nevery step into 1 `test` block,
but also not a Jest unit-test-style\r\ndesign. When it is possible to
group similar actions on the same page\r\nand if it is a part of the
same user flow - we should do it. It also\r\ndoesn't bring the testing
value repeating the same UI steps multiple\r\ntimes in different
scenarios. _Our CI costs are critical to cut when it\r\nis
possible_\r\n- Avoid **nesting describe** blocks: it complicates test
readability and\r\nalso complicates for CI bot to properly skip the
failing block (it will\r\nskip the top level one). We encourage **Scout
parallel test execution**\r\nbased on running test spec files in
multiple workers, not the `test`\r\nblocks within the same file. Having
too many `test` blocks in the same\r\nfile will be slowly run in the
single thread and in case of flakiness,\r\nit means Team lose more test
coverage than they probably expect.\r\n\r\nBefore (**59** test blocks -
**8-8.5 min** per distro):\r\n<img width=\"1709\" alt=\"Screenshot
2025-02-08 at 18 01
40\"\r\nsrc=\"https://github.com/user-attachments/assets/5fd65a1c-85f9-4594-9dae-3f8e99a005ab\"\r\n/>\r\n\r\nAfter
(**15** test blocks - **3.5-4 min** per distro):\r\n<img width=\"1578\"
alt=\"Screenshot 2025-02-10 at 18 14
42\"\r\nsrc=\"https://github.com/user-attachments/assets/6846898f-7dd2-4f6b-8bc5-d06741b0b120\"\r\n/>\r\n\r\nFor
reviewers: updated tests are possible to run in 2 parallel
workers\r\nagainst the same Kibana/ES instance and run time is dropping
to **2.5-3\r\nmin** 🚀 . It is up to UX-Logs team to decide if you want
to keep\r\nparallel run (new tests can be added either to parallel or
sequential\r\nrun)\r\n<img width=\"1578\" alt=\"Screenshot 2025-02-11 at
12 14
30\"\r\nsrc=\"https://github.com/user-attachments/assets/e94113f2-d7f1-470e-a6d5-cb5154d99c41\"\r\n/>\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"bd13e829498032c07bf8490f770a563f34e9f856"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Dzmitry Lemechko 2025-02-17 20:36:15 +01:00 committed by GitHub
parent 5b1361200c
commit 975485161c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 1086 additions and 1183 deletions

View file

@ -33,7 +33,9 @@ done
# Observability Onboarding
for run_mode in "--stateful"; do
run_tests "Observability Onboarding" "x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/playwright.config.ts" "$run_mode"
run_tests "Observability Onboarding: Parallel Workers" "x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel.playwright.config.ts" "$run_mode"
# Disabled while we don't have any tests under the config
# run_tests "Observability Onboarding" "x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/playwright.config.ts" "$run_mode"
done

1
.github/CODEOWNERS vendored
View file

@ -793,6 +793,7 @@ src/platform/packages/shared/kbn-saved-search-component @elastic/obs-ux-logs-tea
src/platform/plugins/shared/saved_search @elastic/kibana-data-discovery
packages/kbn-scout @elastic/appex-qa
packages/kbn-scout-info @elastic/appex-qa
x-pack/solutions/observability/packages/kbn-scout-oblt @elastic/appex-qa
packages/kbn-scout-reporting @elastic/appex-qa
examples/screenshot_mode_example @elastic/appex-sharedux
src/platform/plugins/shared/screenshot_mode @elastic/appex-sharedux

View file

@ -1495,6 +1495,7 @@
"@kbn/reporting-mocks-server": "link:src/platform/packages/private/kbn-reporting/mocks_server",
"@kbn/scout": "link:packages/kbn-scout",
"@kbn/scout-info": "link:packages/kbn-scout-info",
"@kbn/scout-oblt": "link:x-pack/solutions/observability/packages/kbn-scout-oblt",
"@kbn/scout-reporting": "link:packages/kbn-scout-reporting",
"@kbn/security-api-integration-helpers": "link:x-pack/test/security_api_integration/packages/helpers",
"@kbn/serverless-storybook-config": "link:packages/serverless/storybook/config",

View file

@ -36,3 +36,8 @@ export type {
ScoutServerConfig,
ScoutTestConfig,
} from './src/types';
// re-export from Playwright
export type { Locator } from 'playwright/test';
export { measurePerformance, measurePerformanceAsync } from './src/common';

View file

@ -8,8 +8,9 @@
*/
import { mergeTests } from 'playwright/test';
import { coreWorkerFixtures, scoutSpaceParallelFixture } from './worker';
import { apiFixtures, coreWorkerFixtures, scoutSpaceParallelFixture } from './worker';
import type {
ApiParallelWorkerFixtures,
EsClient,
KbnClient,
KibanaUrl,
@ -29,6 +30,7 @@ export const scoutParallelFixtures = mergeTests(
// worker scope fixtures
coreWorkerFixtures,
scoutSpaceParallelFixture,
apiFixtures,
// test scope fixtures
browserAuthFixture,
scoutPageParallelFixture,
@ -42,7 +44,7 @@ export interface ScoutParallelTestFixtures {
pageObjects: PageObjects;
}
export interface ScoutParallelWorkerFixtures {
export interface ScoutParallelWorkerFixtures extends ApiParallelWorkerFixtures {
log: ScoutLogger;
config: ScoutTestConfig;
kbnUrl: KibanaUrl;

View file

@ -8,7 +8,13 @@
*/
import { mergeTests } from 'playwright/test';
import { coreWorkerFixtures, esArchiverFixture, uiSettingsFixture } from './worker';
import {
ApiFixtures,
apiFixtures,
coreWorkerFixtures,
esArchiverFixture,
uiSettingsFixture,
} from './worker';
import type {
EsArchiverFixture,
EsClient,
@ -34,6 +40,8 @@ export const scoutFixtures = mergeTests(
coreWorkerFixtures,
esArchiverFixture,
uiSettingsFixture,
// api fixtures
apiFixtures,
// test scope fixtures
browserAuthFixture,
scoutPageFixture,
@ -47,7 +55,7 @@ export interface ScoutTestFixtures {
pageObjects: PageObjects;
}
export interface ScoutWorkerFixtures {
export interface ScoutWorkerFixtures extends ApiFixtures {
log: ScoutLogger;
config: ScoutTestConfig;
kbnUrl: KibanaUrl;

View file

@ -8,6 +8,7 @@
*/
import { Page } from '@playwright/test';
import { PathOptions } from '../../../../common/services/kibana_url';
/**
* Extends the Playwright 'Page' interface with methods specific to Kibana.
@ -24,7 +25,7 @@ export type ScoutPage = Page & {
* @param options - Additional navigation options, passed directly to Playwright's `goto` method.
* @returns A Promise resolving to a Playwright `Response` or `null`.
*/
gotoApp: (appName: string, options?: Parameters<Page['goto']>[1]) => ReturnType<Page['goto']>;
gotoApp: (appName: string, pathOptions?: PathOptions) => ReturnType<Page['goto']>;
/**
* Waits for the Kibana loading spinner indicator to disappear.
* @returns A Promise resolving when the indicator is hidden.

View file

@ -8,6 +8,7 @@
*/
import { Page, test as base } from '@playwright/test';
import { PathOptions } from '../../../../common/services/kibana_url';
import { ScoutPage } from '.';
import { KibanaUrl, ScoutLogger } from '../../worker';
import { ScoutSpaceParallelFixture } from '../../worker/scout_space';
@ -29,8 +30,8 @@ export const scoutPageParallelFixture = base.extend<
const extendedPage = extendPlaywrightPage({ page, kbnUrl });
// Overriding navigation to specific Kibana apps: url should respect the Kibana Space id
extendedPage.gotoApp = (appName: string) =>
page.goto(kbnUrl.app(appName, { space: scoutSpace.id }));
extendedPage.gotoApp = (appName: string, pathOptions?: PathOptions) =>
page.goto(kbnUrl.app(appName, { space: scoutSpace.id, pathOptions }));
log.serviceLoaded(`scoutPage:${scoutSpace.id}`);
await use(extendedPage);

View file

@ -9,6 +9,7 @@
import { Page } from '@playwright/test';
import { subj } from '@kbn/test-subj-selector';
import { PathOptions } from '../../../../common/services/kibana_url';
import { KibanaUrl, ScoutLogger, coreWorkerFixtures } from '../../worker';
import { ScoutPage } from '.';
@ -81,7 +82,8 @@ export function extendPlaywrightPage({
// Extend page with '@kbn/test-subj-selector' support
extendedPage.testSubj = extendPageWithTestSubject(page);
// Method to navigate to specific Kibana apps
extendedPage.gotoApp = (appName: string) => page.goto(kbnUrl.app(appName));
extendedPage.gotoApp = (appName: string, pathOptions?: PathOptions) =>
page.goto(kbnUrl.app(appName, { pathOptions }));
// Method to wait for global loading indicator to be hidden
extendedPage.waitForLoadingIndicatorHidden = () =>
extendedPage.testSubj.waitForSelector('globalLoadingIndicator-hidden', {

View file

@ -0,0 +1,71 @@
/*
* 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 { measurePerformanceAsync } from '../../../../../common';
import { coreWorkerFixtures } from '../../core_fixtures';
export interface FleetApiFixture {
integration: {
install: (name: string) => Promise<void>;
delete: (name: string) => Promise<void>;
};
}
/**
* This fixture provides a helper to interact with the Fleet API.
*/
export const fleetApiFixture = coreWorkerFixtures.extend<{}, { fleetApi: FleetApiFixture }>({
fleetApi: [
async ({ kbnClient, log }, use) => {
const fleetApiHelper = {
integration: {
install: async (name: string) => {
await measurePerformanceAsync(
log,
`fleetApi.integration.install [${name}]`,
async () => {
await kbnClient.request({
method: 'POST',
path: `/api/fleet/epm/custom_integrations`,
body: {
force: true,
integrationName: name,
datasets: [
{ name: `${name}.access`, type: 'logs' },
{ name: `${name}.error`, type: 'metrics' },
{ name: `${name}.warning`, type: 'logs' },
],
},
});
}
);
},
delete: async (name: string) => {
await measurePerformanceAsync(
log,
`fleetApi.integration.delete [${name}]`,
async () => {
await kbnClient.request({
method: 'DELETE',
path: `/api/fleet/epm/packages/${name}`,
ignoreErrors: [400],
});
}
);
},
},
};
log.serviceLoaded('fleetApi');
await use(fleetApiHelper);
},
{ scope: 'worker' },
],
});

View file

@ -0,0 +1,21 @@
/*
* 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 { mergeTests } from 'playwright/test';
import { FleetApiFixture, fleetApiFixture } from './fleet';
export const apiFixtures = mergeTests(fleetApiFixture);
export interface ApiFixtures {
fleetApi: FleetApiFixture;
}
export interface ApiParallelWorkerFixtures {
fleetApi: FleetApiFixture;
}

View file

@ -25,3 +25,6 @@ export type { UiSettingsFixture } from './ui_settings';
export { scoutSpaceParallelFixture } from './scout_space';
export type { ScoutSpaceParallelFixture } from './scout_space';
export { apiFixtures } from './apis';
export type { ApiFixtures, ApiParallelWorkerFixtures } from './apis';

View file

@ -1580,6 +1580,8 @@
"@kbn/scout/*": ["packages/kbn-scout/*"],
"@kbn/scout-info": ["packages/kbn-scout-info"],
"@kbn/scout-info/*": ["packages/kbn-scout-info/*"],
"@kbn/scout-oblt": ["x-pack/solutions/observability/packages/kbn-scout-oblt"],
"@kbn/scout-oblt/*": ["x-pack/solutions/observability/packages/kbn-scout-oblt/*"],
"@kbn/scout-reporting": ["packages/kbn-scout-reporting"],
"@kbn/scout-reporting/*": ["packages/kbn-scout-reporting/*"],
"@kbn/screenshot-mode-example-plugin": ["examples/screenshot_mode_example"],

View file

@ -0,0 +1,45 @@
# @kbn/scout-oblt
`@kbn/scout-oblt` is a test library that extends `@kbn/scout` with test helpers specifically designed for `Observability` products in Kibana.
Its primary goal is to simplify the test development experience for teams working on `Observability` plugins by providing custom Playwright fixtures, page objects, and utilities tailored for Observability-related testing scenarios.
### Table of Contents
1. Folder Structure
2. How to Use
3. Contributing
### Folder Structure
The `@kbn/scout-oblt` structure includes the following key directories and files:
```
x-pack/solutions/observability/packages/kbn-scout-oblt/
├── src/
│ ├── playwright/
│ │ └── fixtures
│ │ │ └── test/
│ │ │ │ └── // Observability test-scope fixtures
│ │ │ └── worker/
│ │ │ │ └── // Observability worker-scope fixtures
│ │ │ └── single_thread_fixtures.ts
│ │ │ └── parallel_run_fixtures.ts
│ │ │ └── index.ts
│ │ └── page_objects/
│ │ │ └── // Observability pages
│ └── index.ts
├── package.json
├── tsconfig.json
```
### How to use
```
import { test } from '@kbn/scout-oblt';
test('verifies Observability Home loads', async ({ page, pageObjects }) => {
await pageObjects.onboardingHome.goto();
expect(await page.title()).toContain('Observability');
});
```

View file

@ -0,0 +1,38 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { test, spaceTest } from './src/playwright';
export type {
ObltPageObjects,
ObltTestFixtures,
ObltWorkerFixtures,
ObltParallelTestFixtures,
ObltParallelWorkerFixtures,
} from './src/playwright';
// re-export from @kbn/scout
export {
expect,
tags,
createPlaywrightConfig,
createLazyPageObject,
ingestTestDataHook,
} from '@kbn/scout';
export type {
EsClient,
KbnClient,
KibanaUrl,
ScoutLogger,
ScoutPage,
PageObjects,
ScoutServerConfig,
ScoutTestConfig,
ScoutPlaywrightOptions,
ScoutTestOptions,
Locator,
} from '@kbn/scout';

View file

@ -0,0 +1,12 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/solutions/observability/packages/kbn-scout-oblt'],
};

View file

@ -0,0 +1,6 @@
{
"type": "test-helper",
"id": "@kbn/scout-oblt",
"owner": "@elastic/appex-qa",
"devOnly": true
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/scout-oblt",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -0,0 +1,10 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { test } from './single_thread_fixtures';
export { spaceTest } from './parallel_run_fixtures';
export * from './types';

View file

@ -0,0 +1,30 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { spaceTest as spaceBase } from '@kbn/scout';
import { extendPageObjects } from '../page_objects';
import { ObltParallelTestFixtures, ObltParallelWorkerFixtures } from './types';
/**
* Should be used test spec files, running in parallel in isolated spaces agaist the same Kibana instance.
*/
export const spaceTest = spaceBase.extend<ObltParallelTestFixtures, ObltParallelWorkerFixtures>({
pageObjects: async (
{
pageObjects,
page,
}: {
pageObjects: ObltParallelTestFixtures['pageObjects'];
page: ObltParallelTestFixtures['page'];
},
use: (pageObjects: ObltParallelTestFixtures['pageObjects']) => Promise<void>
) => {
const extendedPageObjects = extendPageObjects(pageObjects, page);
await use(extendedPageObjects);
},
});

View file

@ -0,0 +1,30 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { test as base } from '@kbn/scout';
import { extendPageObjects } from '../page_objects';
import { ObltTestFixtures, ObltWorkerFixtures } from './types';
/**
* Should be used for the test spec files executed seqentially.
*/
export const test = base.extend<ObltTestFixtures, ObltWorkerFixtures>({
pageObjects: async (
{
pageObjects,
page,
}: {
pageObjects: ObltTestFixtures['pageObjects'];
page: ObltTestFixtures['page'];
},
use: (pageObjects: ObltTestFixtures['pageObjects']) => Promise<void>
) => {
const extendedPageObjects = extendPageObjects(pageObjects, page);
await use(extendedPageObjects);
},
});

View file

@ -0,0 +1,26 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
ScoutParallelTestFixtures,
ScoutParallelWorkerFixtures,
ScoutTestFixtures,
ScoutWorkerFixtures,
} from '@kbn/scout';
import { ObltPageObjects } from '../page_objects';
export interface ObltTestFixtures extends ScoutTestFixtures {
pageObjects: ObltPageObjects;
}
export type ObltWorkerFixtures = ScoutWorkerFixtures;
export interface ObltParallelTestFixtures extends ScoutParallelTestFixtures {
pageObjects: ObltPageObjects;
}
export type ObltParallelWorkerFixtures = ScoutParallelWorkerFixtures;

View file

@ -5,4 +5,5 @@
* 2.0.
*/
export { OnboardingHomePage } from './onboarding_home';
export * from './fixtures';
export type { ObltPageObjects } from './page_objects';

View file

@ -0,0 +1,125 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ScoutPage, Locator } from '@kbn/scout';
export class CustomLogsPage {
public advancedSettingsContent: Locator;
public logFilePathList: Locator;
public addLogFilePathButton: Locator;
public integrationNameInput: Locator;
public datasetNameInput: Locator;
public serviceNameInput: Locator;
public namespaceInput: Locator;
public customConfigInput: Locator;
public customIntegrationInstalledCallout: Locator;
public customIntegrationErrorCallout: Locator;
public apiKeyCreatedCallout: Locator;
public apiKeyPrivilegesErrorCallout: Locator;
public apiKeyCreateErrorCallout: Locator;
public autoDownloadConfigurationToggle: Locator;
public autoDownloadConfigurationCallout: Locator;
public installCodeSnippet: Locator;
public windowsInstallElasticAgentDocLink: Locator;
public configureElasticAgentStep: Locator;
public downloadConfigurationButton: Locator;
public continueButton: Locator;
public exploreLogsButton: Locator;
public checkLogsStepMessage: Locator;
constructor(private readonly page: ScoutPage) {
this.advancedSettingsContent = this.page.testSubj
.locator('obltOnboardingCustomLogsAdvancedSettings')
.getByRole('group');
this.logFilePathList = this.page.locator(`[data-test-subj^=obltOnboardingLogFilePath-]`);
this.addLogFilePathButton = this.page.testSubj.locator('obltOnboardingCustomLogsAddFilePath');
this.integrationNameInput = this.page.testSubj.locator(
'obltOnboardingCustomLogsIntegrationsName'
);
this.datasetNameInput = this.page.testSubj.locator('obltOnboardingCustomLogsDatasetName');
this.serviceNameInput = this.page.testSubj.locator('obltOnboardingCustomLogsServiceName');
this.namespaceInput = this.page.testSubj.locator('obltOnboardingCustomLogsNamespace');
this.continueButton = page.testSubj.locator('obltOnboardingCustomLogsContinue');
this.customConfigInput = this.page.testSubj.locator('obltOnboardingCustomLogsCustomConfig');
this.customIntegrationInstalledCallout = this.page.testSubj.locator(
'obltOnboardingCustomIntegrationInstalled'
);
this.customIntegrationErrorCallout = this.page.testSubj.locator(
'obltOnboardingCustomIntegrationErrorCallout'
);
this.apiKeyCreatedCallout = this.page.testSubj.locator('obltOnboardingLogsApiKeyCreated');
this.apiKeyPrivilegesErrorCallout = this.page.testSubj.locator(
'obltOnboardingLogsApiKeyCreationNoPrivileges'
);
this.apiKeyCreateErrorCallout = this.page.testSubj.locator(
'obltOnboardingLogsApiKeyCreationFailed'
);
this.autoDownloadConfigurationToggle = this.page.testSubj.locator(
'obltOnboardingInstallElasticAgentAutoDownloadConfig'
);
this.autoDownloadConfigurationCallout = this.page.testSubj.locator(
'obltOnboardingInstallElasticAgentAutoDownloadConfigCallout'
);
this.installCodeSnippet = this.page.testSubj
.locator('obltOnboardingInstallElasticAgentStep')
.getByRole('code');
this.windowsInstallElasticAgentDocLink = this.page.testSubj.locator(
'obltOnboardingInstallElasticAgentWindowsDocsLink'
);
this.configureElasticAgentStep = this.page.testSubj.locator(
'obltOnboardingConfigureElasticAgentStep'
);
this.downloadConfigurationButton = this.page.testSubj.locator(
'obltOnboardingConfigureElasticAgentStepDownloadConfig'
);
this.exploreLogsButton = this.page.testSubj.locator('obltOnboardingExploreLogs');
this.checkLogsStepMessage = this.page.testSubj
.locator('obltOnboardingCheckLogsStep')
.locator(`.euiStep__title`);
}
async goto() {
return this.page.gotoApp('observabilityOnboarding/customLogs');
}
async clickBackButton() {
return this.page.testSubj.click('observabilityOnboardingFlowBackToSelectionButton');
}
getLogFilePathInputField(index: number) {
return this.page.testSubj.locator(`obltOnboardingLogFilePath-${index}`).getByRole('textbox');
}
logFilePathDeleteButton(index: number) {
return this.page.testSubj.locator(`obltOnboardingLogFilePathDelete-${index}`);
}
async clickAdvancedSettingsButton() {
return this.page.testSubj
.locator('obltOnboardingCustomLogsAdvancedSettings')
.getByRole('button')
.first()
.click();
}
getStepStatusLocator(status: 'loading' | 'complete' | 'danger' | 'warning') {
return this.page.testSubj.locator(`obltOnboardingStepStatus-${status}`);
}
async selectPlatform(name: 'linux' | 'macos' | 'windows') {
return this.page.testSubj.click(name);
}
getCheckLogsStepLocator(status: 'loading' | 'incomplete' | 'complete') {
return this.page.testSubj
.locator('obltOnboardingCheckLogsStep')
.locator(`.euiStep__titleWrapper [class$="euiStepNumber-s-${status}"]`);
}
}

View file

@ -0,0 +1,23 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PageObjects, ScoutPage, createLazyPageObject } from '@kbn/scout';
import { OnboardingHomePage } from './onboarding_home';
import { CustomLogsPage } from './custom_logs';
export interface ObltPageObjects extends PageObjects {
onboardingHome: OnboardingHomePage;
customLogs: CustomLogsPage;
}
export function extendPageObjects(pageObjects: PageObjects, page: ScoutPage): ObltPageObjects {
return {
...pageObjects,
onboardingHome: createLazyPageObject(OnboardingHomePage, page),
customLogs: createLazyPageObject(CustomLogsPage, page),
};
}

View file

@ -0,0 +1,19 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node"
]
},
"include": [
"**/*.ts",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/scout",
]
}

View file

@ -12,6 +12,8 @@ node scripts/scout.js start-server --serverless=[es|oblt|security]
Then you can run the tests in another terminal:
Some tests are designed to run sequentially:
```bash
// ESS
npx playwright test --config x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/playwright.config.ts --grep @ess
@ -20,4 +22,14 @@ npx playwright test --config x-pack/solutions/observability/plugins/observabilit
npx playwright test --config x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/playwright.config.ts --grep @svlOblt
```
Some tests are designed to run concurrently (preferred option):
```bash
// ESS
npx playwright test --config x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_playwright.config.ts --grep @ess
// Serverless
npx playwright test --config x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/parallel_playwright.config.ts --grep @svlOblt
```
Test results are available in `x-pack/solutions/observability/plugins/observability_onboarding/ui_tests/output`

View file

@ -0,0 +1,11 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const FIELD_VALIDATION = {
INTEGRATION_NAME_LOWERCASE: 'An integration name should be lowercase.',
DATASET_NAME_LOWERCASE: 'A dataset name should be lowercase.',
};

View file

@ -5,46 +5,35 @@
* 2.0.
*/
import {
test as base,
PageObjects,
createLazyPageObject,
ScoutTestFixtures,
ScoutWorkerFixtures,
KibanaUrl,
KbnClient,
} from '@kbn/scout';
import { OnboardingHomePage } from './page_objects';
import { CustomLogsPage } from './page_objects/custom_logs';
import { v4 as uuidv4 } from 'uuid';
import { test as base, ObltTestFixtures, ObltWorkerFixtures } from '@kbn/scout-oblt';
import { mergeTests } from 'playwright/test';
import { onboardingApiFixture, OnboardingApiFixture } from './onboarding_api';
export interface ExtendedScoutTestFixtures extends ScoutTestFixtures {
pageObjects: PageObjects & {
onboardingHomePage: OnboardingHomePage;
customLogsPage: CustomLogsPage;
};
export type ExtendedScoutTestFixtures = ObltTestFixtures;
export interface ExtendedScoutWorkerFixtures extends ObltWorkerFixtures {
onboardingApi: OnboardingApiFixture;
}
export const test = base.extend<ExtendedScoutTestFixtures, ScoutWorkerFixtures>({
const testFixtures = mergeTests(base, onboardingApiFixture);
export const test = testFixtures.extend<ExtendedScoutTestFixtures, ExtendedScoutWorkerFixtures>({
pageObjects: async (
{
pageObjects,
page,
kbnUrl,
kbnClient,
}: {
pageObjects: ExtendedScoutTestFixtures['pageObjects'];
page: ExtendedScoutTestFixtures['page'];
kbnUrl: KibanaUrl;
kbnClient: KbnClient;
},
use: (pageObjects: ExtendedScoutTestFixtures['pageObjects']) => Promise<void>
) => {
const extendedPageObjects = {
...pageObjects,
onboardingHomePage: createLazyPageObject(OnboardingHomePage, page),
customLogsPage: createLazyPageObject(CustomLogsPage, page, kbnUrl, kbnClient),
};
await use(extendedPageObjects);
},
});
export const generateIntegrationName = (name: string) => `${name}_${uuidv4().slice(0, 5)}`;
export * as assertionMessages from './assertion_messages';

View file

@ -0,0 +1,48 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { test as base } from '@kbn/scout-oblt';
export interface OnboardingApiFixture {
updateInstallationStepStatus: (
onboardingId: string,
step: string,
status: string,
payload?: object
) => Promise<void>;
}
/**
* This fixture provides a helper to interact with the Observability Onboarding API.
*/
export const onboardingApiFixture = base.extend<{}, { onboardingApi: OnboardingApiFixture }>({
onboardingApi: [
async ({ kbnClient, log }, use) => {
const onboardingApiHelper: OnboardingApiFixture = {
updateInstallationStepStatus: async (
onboardingId: string,
step: string,
status: string,
payload?: object
) => {
await kbnClient.request({
method: 'POST',
path: `/internal/observability_onboarding/flow/${onboardingId}/step/${step}`,
body: {
status,
payload,
},
});
},
};
log.serviceLoaded('onboardingApi');
await use(onboardingApiHelper);
},
{ scope: 'worker' },
],
});

View file

@ -1,195 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ScoutPage, KibanaUrl, KbnClient } from '@kbn/scout';
export class CustomLogsPage {
static readonly ASSERTION_MESSAGES = {
INTEGRATION_NAME_CASE_ERROR: 'An integration name should be lowercase.',
DATASET_NAME_CASE_ERROR: 'A dataset name should be lowercase.',
EXISTING_INTEGRATION_ERROR: (name: string) =>
`Failed to create the integration as an installation with the name ${name} already exists.`,
DOWNLOADING_AGENT_STATUS: 'Downloading Elastic Agent',
DOWNLOADED_AGENT_STATUS: 'Elastic Agent downloaded',
DOWNLOAD_AGENT_DANGER_CALLOUT: 'Download Elastic Agent',
EXTRACTING_AGENT_STATUS: 'Extracting Elastic Agent',
EXTRACTED_AGENT_STATUS: 'Elastic Agent extracted',
EXTRACT_AGENT_DANGER_CALLOUT: 'Extract Elastic Agent',
INSTALLING_AGENT_STATUS: 'Installing Elastic Agent',
INSTALLED_AGENT_STATUS: 'Elastic Agent installed',
INSTALL_AGENT_DANGER_CALLOUT: 'Install Elastic Agent',
CONNECTING_TO_AGENT_STATUS: 'Connecting to the Elastic Agent',
CONNECTED_TO_AGENT_STATUS: 'Connected to the Elastic Agent',
CONNECT_AGENT_WARNING_CALLOUT: 'Connect to the Elastic Agent',
DOWNLOAD_AGENT_CONFIG_STATUS: 'Downloading Elastic Agent config',
CONFIGURE_AGENT_WARNING_CALLOUT: 'Configure the agent',
DOWNLOADING_AGENT_CONFIG_STATUS: 'Downloading Elastic Agent config',
AGENT_CONFIGURATION_SUCCESS_CALLOUT_MACOS:
'Elastic Agent config written to /Library/Elastic/Agent/elastic-agent.yml',
AGENT_CONFIGURATION_SUCCESS_CALLOUT_LINUX:
'Elastic Agent config written to /opt/Elastic/Agent/elastic-agent.yml',
INSTALLATION_STEP_2_DISABLED: 'Step 2 is disabled',
};
public readonly advancedSettingsContent;
public readonly logFilePathList;
public readonly continueButton;
public readonly addLogFilePathButton;
public readonly integrationNameInput;
public readonly datasetNameInput;
public readonly serviceNameInput;
public readonly namespaceInput;
public readonly customConfigInput;
public readonly customIntegrationSuccessCallout;
public readonly customIntegrationErrorCallout;
public readonly apiKeyCreateSuccessCallout;
public readonly apiKeyPrivilegesErrorCallout;
public readonly apiKeyCreateErrorCallout;
public readonly linuxCodeSnippetButton;
public readonly macOSCodeSnippetButton;
public readonly windowsCodeSnippetButton;
public readonly autoDownloadConfigurationToggle;
public readonly autoDownloadConfigurationCallout;
public readonly installCodeSnippet;
public readonly windowsInstallElasticAgentDocLink;
public readonly configureElasticAgentStep;
public readonly downloadConfigurationButton;
public readonly stepStatusLoading;
public readonly stepStatusComplete;
public readonly stepStatusDanger;
public readonly stepStatusWarning;
constructor(
private readonly page: ScoutPage,
private readonly kbnUrl: KibanaUrl,
private readonly kbnClient: KbnClient
) {
this.advancedSettingsContent = this.page.testSubj
.locator('obltOnboardingCustomLogsAdvancedSettings')
.getByRole('group');
this.logFilePathList = this.page.locator(`[data-test-subj^=obltOnboardingLogFilePath-]`);
this.continueButton = this.page.testSubj.locator('obltOnboardingCustomLogsContinue');
this.addLogFilePathButton = this.page.testSubj.locator('obltOnboardingCustomLogsAddFilePath');
this.integrationNameInput = this.page.testSubj.locator(
'obltOnboardingCustomLogsIntegrationsName'
);
this.datasetNameInput = this.page.testSubj.locator('obltOnboardingCustomLogsDatasetName');
this.serviceNameInput = this.page.testSubj.locator('obltOnboardingCustomLogsServiceName');
this.namespaceInput = this.page.testSubj.locator('obltOnboardingCustomLogsNamespace');
this.customConfigInput = this.page.testSubj.locator('obltOnboardingCustomLogsCustomConfig');
this.customIntegrationSuccessCallout = this.page.testSubj.locator(
'obltOnboardingCustomIntegrationInstalled'
);
this.customIntegrationErrorCallout = this.page.testSubj.locator(
'obltOnboardingCustomIntegrationErrorCallout'
);
this.apiKeyCreateSuccessCallout = this.page.testSubj.locator('obltOnboardingLogsApiKeyCreated');
this.apiKeyPrivilegesErrorCallout = this.page.testSubj.locator(
'obltOnboardingLogsApiKeyCreationNoPrivileges'
);
this.apiKeyCreateErrorCallout = this.page.testSubj.locator(
'obltOnboardingLogsApiKeyCreationFailed'
);
this.linuxCodeSnippetButton = this.page.testSubj.locator('linux-tar');
this.macOSCodeSnippetButton = this.page.testSubj.locator('macos');
this.windowsCodeSnippetButton = this.page.testSubj.locator('windows');
this.autoDownloadConfigurationToggle = this.page.testSubj.locator(
'obltOnboardingInstallElasticAgentAutoDownloadConfig'
);
this.autoDownloadConfigurationCallout = this.page.testSubj.locator(
'obltOnboardingInstallElasticAgentAutoDownloadConfigCallout'
);
this.installCodeSnippet = this.page.testSubj
.locator('obltOnboardingInstallElasticAgentStep')
.getByRole('code');
this.windowsInstallElasticAgentDocLink = this.page.testSubj.locator(
'obltOnboardingInstallElasticAgentWindowsDocsLink'
);
this.configureElasticAgentStep = this.page.testSubj.locator(
'obltOnboardingConfigureElasticAgentStep'
);
this.downloadConfigurationButton = this.page.testSubj.locator(
'obltOnboardingConfigureElasticAgentStepDownloadConfig'
);
this.stepStatusLoading = this.page.testSubj.locator('obltOnboardingStepStatus-loading');
this.stepStatusComplete = this.page.testSubj.locator('obltOnboardingStepStatus-complete');
this.stepStatusDanger = this.page.testSubj.locator('obltOnboardingStepStatus-danger');
this.stepStatusWarning = this.page.testSubj.locator('obltOnboardingStepStatus-warning');
}
async goto() {
this.page.goto(`${this.kbnUrl.app('observabilityOnboarding')}/customLogs`);
}
async clickBackButton() {
await this.page.testSubj.click('observabilityOnboardingFlowBackToSelectionButton');
}
logFilePathInput(index: number) {
return this.page.testSubj.locator(`obltOnboardingLogFilePath-${index}`).getByRole('textbox');
}
logFilePathDeleteButton(index: number) {
return this.page.testSubj.locator(`obltOnboardingLogFilePathDelete-${index}`);
}
async clickAdvancedSettingsButton() {
return this.page.testSubj
.locator('obltOnboardingCustomLogsAdvancedSettings')
.getByRole('button')
.first()
.click();
}
async installCustomIntegration(name: string) {
await this.kbnClient.request({
method: 'POST',
path: `/api/fleet/epm/custom_integrations`,
body: {
force: true,
integrationName: name,
datasets: [
{ name: `${name}.access`, type: 'logs' },
{ name: `${name}.error`, type: 'metrics' },
{ name: `${name}.warning`, type: 'logs' },
],
},
});
}
async deleteIntegration(name: string) {
const packageInfo = await this.kbnClient.request<{ item: { status: string } }>({
method: 'GET',
path: `/api/fleet/epm/packages/${name}`,
ignoreErrors: [404],
});
if (packageInfo.data.item?.status === 'installed') {
await this.kbnClient.request({
method: 'DELETE',
path: `/api/fleet/epm/packages/${name}`,
});
}
}
async updateInstallationStepStatus(
onboardingId: string,
step: string,
status: string,
payload?: object
) {
await this.kbnClient.request({
method: 'POST',
path: `/internal/observability_onboarding/flow/${onboardingId}/step/${step}`,
body: {
status,
payload,
},
});
}
}

View file

@ -0,0 +1,14 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createPlaywrightConfig } from '@kbn/scout';
// eslint-disable-next-line import/no-default-export
export default createPlaywrightConfig({
testDir: './parallel_tests',
workers: 2,
});

View file

@ -0,0 +1,83 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout-oblt';
import { generateIntegrationName, test } from '../../fixtures';
test.describe(
'Observability Onboarding - Custom Integration',
{ tag: ['@ess', '@svlOblt'] },
() => {
const integrationName = generateIntegrationName('mylogs');
const logsFilePath = `${integrationName}.log`;
test.beforeEach(async ({ browserAuth, pageObjects: { customLogs } }) => {
await browserAuth.loginAsAdmin();
await customLogs.goto();
});
test.afterEach(async ({ fleetApi }) => {
await fleetApi.integration.delete(integrationName);
});
test('should be installed, show API Key and correct instructions', async ({
pageObjects: { customLogs },
page,
}) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await expect(customLogs.integrationNameInput).toHaveValue(integrationName);
await expect(customLogs.datasetNameInput).toHaveValue(integrationName);
await expect(customLogs.serviceNameInput).toHaveValue('');
await customLogs.continueButton.click();
await expect(
customLogs.customIntegrationInstalledCallout,
`'mylogs integration installed' should be displayed`
).toBeVisible();
await expect(
customLogs.apiKeyCreatedCallout,
`'API Key created' should be displayed`
).toBeVisible();
// validate default 'Install the Elastic Agent' instructions
await expect(page.testSubj.locator('linux-tar')).toHaveAttribute('aria-pressed', 'true');
await expect(customLogs.autoDownloadConfigurationToggle).not.toBeChecked();
await expect(customLogs.installCodeSnippet).toBeVisible();
await customLogs.selectPlatform('macos');
await expect(customLogs.autoDownloadConfigurationToggle).not.toBeChecked();
await expect(customLogs.installCodeSnippet).toBeVisible();
await customLogs.selectPlatform('windows');
await expect(customLogs.autoDownloadConfigurationToggle).toBeDisabled();
await expect(customLogs.windowsInstallElasticAgentDocLink).toBeVisible();
await expect(customLogs.installCodeSnippet).not.toBeVisible();
await expect(
customLogs.configureElasticAgentStep.getByText('Step 2 is disabled')
).toBeVisible();
});
test('should update instructions when automatic config download toggled', async ({
pageObjects: { customLogs },
}) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await customLogs.autoDownloadConfigurationToggle.click();
await expect(customLogs.autoDownloadConfigurationCallout).toBeVisible();
await expect(customLogs.installCodeSnippet).toContainText('autoDownloadConfig=1');
await customLogs.selectPlatform('macos');
await expect(customLogs.autoDownloadConfigurationCallout).toBeVisible();
await expect(customLogs.installCodeSnippet).toContainText('autoDownloadConfig=1');
await customLogs.autoDownloadConfigurationToggle.click();
await expect(customLogs.installCodeSnippet).not.toContainText('autoDownloadConfig=1');
});
}
);

View file

@ -0,0 +1,107 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout-oblt';
import { assertionMessages, generateIntegrationName, test } from '../../fixtures';
test.describe(
'Observability Onboarding - Custom logs configuration',
{ tag: ['@ess', '@svlOblt'] },
() => {
const logsFilePath = `${generateIntegrationName('mylogs')}.log`;
test.beforeEach(async ({ browserAuth, pageObjects: { customLogs } }) => {
await browserAuth.loginAsAdmin();
await customLogs.goto();
});
test('should navigate to the onboarding home page when the back button clicked', async ({
pageObjects: { customLogs },
page,
}) => {
await customLogs.clickBackButton();
expect(page.url()).toContain('/app/observabilityOnboarding');
});
test(`should allow multiple entries for Log File Path`, async ({
pageObjects: { customLogs },
}) => {
await expect(
customLogs.getLogFilePathInputField(0),
'Log File Path should be empty'
).toHaveValue('');
await expect(
customLogs.continueButton,
'Continue button should be disabled when Log File Path is not set'
).toBeDisabled();
await customLogs.addLogFilePathButton.click();
await expect(customLogs.logFilePathList).toHaveCount(2);
await customLogs.logFilePathDeleteButton(1).click();
await expect(customLogs.logFilePathList).toHaveCount(1);
});
test(`should allow updating Advanced Settings`, async ({ pageObjects: { customLogs } }) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await expect(customLogs.advancedSettingsContent).not.toBeVisible();
await customLogs.clickAdvancedSettingsButton();
await expect(
customLogs.advancedSettingsContent,
'Advanced Settings should be opened'
).toBeVisible();
await expect(customLogs.namespaceInput).toHaveValue('default');
await customLogs.namespaceInput.fill('');
await expect(
customLogs.continueButton,
'Continue button should be disabled when Namespace is empty'
).toBeDisabled();
await customLogs.namespaceInput.fill('default');
await expect(customLogs.customConfigInput).toHaveValue('');
await expect(customLogs.continueButton).not.toBeDisabled();
await customLogs.clickAdvancedSettingsButton();
await expect(
customLogs.advancedSettingsContent,
'Advanced Settings should be closed'
).not.toBeVisible();
});
test('should validate Integration Name field', async ({
pageObjects: { customLogs },
page,
}) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.integrationNameInput.fill('');
await expect(customLogs.continueButton).toBeDisabled();
await customLogs.integrationNameInput.fill('hello$world');
await expect(customLogs.integrationNameInput).toHaveValue('hello_world');
await customLogs.integrationNameInput.fill('H3llowOrld');
await expect(
page.getByText(assertionMessages.FIELD_VALIDATION.INTEGRATION_NAME_LOWERCASE)
).toBeVisible();
});
test('should validate DataSet Name field', async ({ pageObjects: { customLogs }, page }) => {
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.datasetNameInput.fill('');
await expect(customLogs.continueButton).toBeDisabled();
await customLogs.datasetNameInput.fill('hello$world');
await expect(customLogs.datasetNameInput).toHaveValue('hello_world');
await customLogs.datasetNameInput.fill('H3llowOrld');
await expect(
page.getByText(assertionMessages.FIELD_VALIDATION.DATASET_NAME_LOWERCASE)
).toBeVisible();
});
}
);

View file

@ -0,0 +1,109 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout-oblt';
import { generateIntegrationName, test } from '../../fixtures';
test.describe(
'Observability Onboarding - Custom Integration Error',
{ tag: ['@ess', '@svlOblt'] },
() => {
const integrationName = generateIntegrationName('mylogs');
const logsFilePath = `${integrationName}.log`;
test.afterEach(async ({ fleetApi }) => {
await fleetApi.integration.delete(integrationName);
});
test('should be displayed when user has no previleges', async ({
browserAuth,
pageObjects: { customLogs },
}) => {
await browserAuth.loginAsPrivilegedUser();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await expect(
customLogs.customIntegrationInstalledCallout,
`'mylogs integration installed' should be displayed`
).toBeVisible();
await expect(
customLogs.apiKeyPrivilegesErrorCallout,
`'User does not have permissions to create API key' should be displayed`
).toBeVisible();
});
test('should be displayed when API Key creation failed', async ({
browserAuth,
pageObjects: { customLogs },
page,
}) => {
await page.route('**/observability_onboarding/logs/flow', (route) => {
route.fulfill({
status: 500,
body: JSON.stringify({
message: 'Internal error',
}),
});
});
await browserAuth.loginAsAdmin();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await expect(
customLogs.customIntegrationInstalledCallout,
`'mylogs integration installed' should be displayed`
).toBeVisible();
await expect(
customLogs.apiKeyCreateErrorCallout,
`'Failed to create API Key' should be displayed`
).toBeVisible();
});
test('should be displayed when integration failed', async ({
browserAuth,
pageObjects: { customLogs },
page,
kbnUrl,
}) => {
await page.route(kbnUrl.get('/api/fleet/epm/custom_integrations'), (route) => {
route.fulfill({
status: 500,
json: {
message: 'Internal error',
},
});
});
await browserAuth.loginAsAdmin();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
await expect(customLogs.customIntegrationErrorCallout).toBeVisible();
});
test('should be displayed when integration with the same name exists', async ({
browserAuth,
pageObjects: { customLogs },
fleetApi,
page,
}) => {
await fleetApi.integration.install(integrationName);
await browserAuth.loginAsAdmin();
await customLogs.goto();
await customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await customLogs.continueButton.click();
const errorMessage = `Failed to create the integration as an installation with the name ${integrationName} already exists.`;
await expect(page.getByText(errorMessage)).toBeVisible();
});
}
);

View file

@ -0,0 +1,181 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect, Locator } from '@kbn/scout-oblt';
import { generateIntegrationName, test } from '../../fixtures';
const checkAgentStatusUpdated = async (locator: Locator, status: string) =>
expect(locator.getByText(status)).toBeVisible();
test.describe('Observability Onboarding - Elastic Agent', { tag: ['@ess', '@svlOblt'] }, () => {
const integrationName = generateIntegrationName('mylogs');
const logsFilePath = `${integrationName}.log`;
let onboardingId: string;
test.beforeEach(async ({ browserAuth, pageObjects, page }) => {
await page.route('**/observability_onboarding/logs/flow', async (route) => {
const response = await route.fetch();
const body = await response.json();
onboardingId = body.onboardingId;
await route.fulfill({ response });
});
// login and create custom integration
await browserAuth.loginAsAdmin();
await pageObjects.customLogs.goto();
await pageObjects.customLogs.getLogFilePathInputField(0).fill(logsFilePath);
await pageObjects.customLogs.continueButton.click();
await pageObjects.customLogs.apiKeyCreatedCallout.waitFor({ state: 'visible' });
});
test.afterEach(async ({ fleetApi }) => {
await fleetApi.integration.delete(integrationName);
});
test('should be installed sucessfully', async ({
pageObjects: { customLogs },
onboardingApi,
}) => {
// downloading agent
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-download', 'loading');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Downloading Elastic Agent'
);
// downloading agent failed
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-download', 'danger');
await expect(customLogs.getStepStatusLocator('danger'), 'Download Elastic Agent').toBeVisible();
// downloading agent completed
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-download', 'complete');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent downloaded'
);
// extracting agent
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-extract', 'loading');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Extracting Elastic Agent'
);
// extracting agent completed
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent extracted'
);
// installing agent failed
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-install', 'danger');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('danger'),
'Install Elastic Agent'
);
// installing agent completed
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent installed'
);
// agent connecting
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-status', 'loading');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Connecting to the Elastic Agent'
);
await expect(customLogs.getCheckLogsStepLocator('incomplete')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Ship logs to Elastic Observability');
// agent connected
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete', {
agentId: 'test-agent-id',
});
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Connected to the Elastic Agent'
);
});
test('should be configured sucessfully for Linux and wait for logs', async ({
pageObjects: { customLogs },
onboardingApi,
}) => {
// install and connect agent for linux
await customLogs.autoDownloadConfigurationToggle.click();
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-download', 'complete');
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete');
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete');
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete', {
agentId: 'test-agent-id',
});
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('loading'),
'Downloading Elastic Agent config'
);
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent config written to /opt/Elastic/Agent/elastic-agent.yml'
);
await expect(customLogs.getCheckLogsStepLocator('loading')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Waiting for logs to be shipped...');
});
test('should be configured sucessfully for MacOS and wait for logs', async ({
pageObjects: { customLogs },
onboardingApi,
}) => {
// install and connect agent for macos
await customLogs.selectPlatform('macos');
await customLogs.autoDownloadConfigurationToggle.click();
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-download', 'complete');
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete');
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete');
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete', {
agentId: 'test-agent-id',
});
await onboardingApi.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete');
await checkAgentStatusUpdated(
customLogs.getStepStatusLocator('complete'),
'Elastic Agent config written to /Library/Elastic/Agent/elastic-agent.yml'
);
await expect(customLogs.getCheckLogsStepLocator('loading')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Waiting for logs to be shipped...');
});
test('should ship logs to Elastic', async ({ page, pageObjects: { customLogs } }) => {
await page.route('**/progress', (route) => {
route.fulfill({
status: 200,
body: JSON.stringify({
progress: {
'ea-download': { status: 'complete' },
'ea-extract': { status: 'complete' },
'ea-install': { status: 'complete' },
'ea-status': { status: 'complete' },
'ea-config': { status: 'complete' },
'logs-ingest': { status: 'complete' },
},
}),
});
});
await expect(customLogs.getCheckLogsStepLocator('complete')).toBeVisible();
await expect(customLogs.checkLogsStepMessage).toHaveText('Logs are being shipped!');
await customLogs.exploreLogsButton.click();
await expect(page).toHaveURL(/\/app\/observability-logs-explorer/);
});
});

View file

@ -1,256 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout';
import { test } from '../../fixtures';
import { CustomLogsPage } from '../../fixtures/page_objects/custom_logs';
test.describe('Onboarding app - Custom logs configuration', { tag: ['@ess', '@svlOblt'] }, () => {
test.beforeEach(async ({ browserAuth, pageObjects: { customLogsPage } }) => {
await browserAuth.loginAsAdmin();
await customLogsPage.goto();
});
test('When user clicks the back button user goes to the onboarding home page', async ({
pageObjects: { customLogsPage },
page,
}) => {
await customLogsPage.clickBackButton();
expect(page.url()).toContain('/app/observabilityOnboarding');
});
test(`Users shouldn't be able to continue if logFilePaths is empty`, async ({
pageObjects: { customLogsPage },
}) => {
await expect(customLogsPage.logFilePathInput(0)).toHaveValue('');
await expect(customLogsPage.continueButton).toBeDisabled();
});
test(`Users should be able to continue if logFilePaths is not empty`, async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.logFilePathInput(0).fill('some/path');
await expect(customLogsPage.continueButton).not.toBeDisabled();
});
test('Users can add multiple logFilePaths', async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.addLogFilePathButton.click();
await expect(customLogsPage.logFilePathInput(0)).toBeVisible();
await expect(customLogsPage.logFilePathInput(1)).toBeVisible();
});
test('Users can delete logFilePaths', async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.addLogFilePathButton.click();
await expect(customLogsPage.logFilePathList).toHaveCount(2);
await customLogsPage.logFilePathDeleteButton(1).click();
await expect(customLogsPage.logFilePathList).toHaveCount(1);
});
test('Dataset Name and Integration Name are auto generated if it is the first path', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.logFilePathInput(0).fill('myLogs.log');
await expect(customLogsPage.integrationNameInput).toHaveValue('mylogs');
await expect(customLogsPage.datasetNameInput).toHaveValue('mylogs');
});
test('Dataset Name and Integration Name are not generated if it is not the first path', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.addLogFilePathButton.click();
await customLogsPage.logFilePathInput(1).fill('myLogs.log');
await expect(customLogsPage.integrationNameInput).toHaveValue('');
await expect(customLogsPage.datasetNameInput).toHaveValue('');
});
test('Service name input should be optional allowing user to continue if it is empty', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.logFilePathInput(0).fill('myLogs.log');
await expect(customLogsPage.serviceNameInput).toHaveValue('');
await expect(customLogsPage.continueButton).not.toBeDisabled();
});
test('Users should expand and collapse the Advanced settings section', async ({
pageObjects: { customLogsPage },
}) => {
await expect(customLogsPage.advancedSettingsContent).not.toBeVisible();
await customLogsPage.clickAdvancedSettingsButton();
await expect(customLogsPage.advancedSettingsContent).toBeVisible();
await customLogsPage.clickAdvancedSettingsButton();
await expect(customLogsPage.advancedSettingsContent).not.toBeVisible();
});
test('Users should see a default namespace', async ({ pageObjects: { customLogsPage } }) => {
customLogsPage.clickAdvancedSettingsButton();
await expect(customLogsPage.namespaceInput).toHaveValue('default');
});
test('Users should not be able to continue if they do not specify a namespace', async ({
pageObjects: { customLogsPage },
}) => {
customLogsPage.clickAdvancedSettingsButton();
await customLogsPage.namespaceInput.fill('');
await expect(customLogsPage.continueButton).toBeDisabled();
});
test('should be optional allowing user to continue if it is empty', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.clickAdvancedSettingsButton();
await customLogsPage.logFilePathInput(0).fill('myLogs.log');
await expect(customLogsPage.customConfigInput).toHaveValue('');
await expect(customLogsPage.continueButton).not.toBeDisabled();
});
test.describe('Integration name', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.logFilePathInput(0).fill('myLogs.log');
});
test('Users should not be able to continue if they do not specify an integrationName', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.integrationNameInput.fill('');
await expect(customLogsPage.continueButton).toBeDisabled();
});
test('value will contain _ instead of special chars', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.integrationNameInput.fill('hello$world');
await expect(customLogsPage.integrationNameInput).toHaveValue('hello_world');
});
test('value will be invalid if it is not lowercase', async ({
pageObjects: { customLogsPage },
page,
}) => {
await customLogsPage.integrationNameInput.fill('H3llowOrld');
await expect(
page.getByText(CustomLogsPage.ASSERTION_MESSAGES.INTEGRATION_NAME_CASE_ERROR)
).toBeVisible();
});
});
test.describe('datasetName', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.logFilePathInput(0).fill('myLogs.log');
});
test('Users should not be able to continue if they do not specify a datasetName', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.datasetNameInput.fill('');
await expect(customLogsPage.continueButton).toBeDisabled();
});
test('value will contain _ instead of special chars', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.datasetNameInput.fill('hello$world');
await expect(customLogsPage.datasetNameInput).toHaveValue('hello_world');
});
test('value will be invalid if it is not lowercase', async ({
pageObjects: { customLogsPage },
page,
}) => {
await customLogsPage.datasetNameInput.fill('H3llowOrld');
await expect(
page.getByText(CustomLogsPage.ASSERTION_MESSAGES.DATASET_NAME_CASE_ERROR)
).toBeVisible();
});
});
test.describe('Custom integration', () => {
const CUSTOM_INTEGRATION_NAME = 'mylogs';
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.deleteIntegration(CUSTOM_INTEGRATION_NAME);
});
test.describe('when user is missing privileges', () => {
test.beforeEach(async ({ browserAuth, page }) => {
await browserAuth.loginAsViewer();
await page.reload();
});
test.afterEach(async ({ browserAuth, page }) => {
await browserAuth.loginAsAdmin();
await page.reload();
});
test('installation fails', async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.logFilePathInput(0).fill(`${CUSTOM_INTEGRATION_NAME}.log`);
await customLogsPage.continueButton.click();
await expect(customLogsPage.customIntegrationErrorCallout).toBeVisible();
});
});
test.describe('when user has proper privileges', () => {
test('installation succeed and user is redirected to install elastic agent step', async ({
page,
pageObjects: { customLogsPage },
}) => {
await customLogsPage.logFilePathInput(0).fill(`${CUSTOM_INTEGRATION_NAME}.log`);
await customLogsPage.continueButton.click();
await page.waitForURL('**/app/observabilityOnboarding/customLogs/installElasticAgent');
});
});
test('installation fails if integration already exists', async ({
pageObjects: { customLogsPage },
page,
}) => {
await customLogsPage.installCustomIntegration(CUSTOM_INTEGRATION_NAME);
await customLogsPage.logFilePathInput(0).fill(`${CUSTOM_INTEGRATION_NAME}.log`);
await customLogsPage.continueButton.click();
await expect(
page.getByText(
CustomLogsPage.ASSERTION_MESSAGES.EXISTING_INTEGRATION_ERROR(CUSTOM_INTEGRATION_NAME)
)
).toBeVisible();
});
test.describe('when an error occurred on creation', () => {
test('user should see the error displayed', async ({
pageObjects: { customLogsPage },
page,
kbnUrl,
}) => {
await page.route(kbnUrl.get('/api/fleet/epm/custom_integrations'), (route) => {
route.fulfill({
status: 500,
json: {
message: 'Internal error',
},
});
});
await customLogsPage.logFilePathInput(0).fill(`${CUSTOM_INTEGRATION_NAME}.log`);
await customLogsPage.continueButton.click();
await expect(customLogsPage.customIntegrationErrorCallout).toBeVisible();
});
});
});
});

View file

@ -1,696 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { expect } from '@kbn/scout';
import { type ExtendedScoutTestFixtures, test } from '../../fixtures';
import { CustomLogsPage } from '../../fixtures/page_objects/custom_logs';
const CUSTOM_INTEGRATION_NAME = 'mylogs';
function setupPage({ isAdmin }: { isAdmin: boolean }) {
return async ({
browserAuth,
pageObjects: { customLogsPage },
page,
}: ExtendedScoutTestFixtures) => {
await browserAuth.loginAsAdmin();
await customLogsPage.deleteIntegration(CUSTOM_INTEGRATION_NAME);
if (!isAdmin) {
await browserAuth.loginAsPrivilegedUser();
await page.reload();
}
await customLogsPage.deleteIntegration(CUSTOM_INTEGRATION_NAME);
await customLogsPage.goto();
await customLogsPage.logFilePathInput(0).fill('mylogs.log');
await customLogsPage.continueButton.click();
};
}
test.describe(
'Onboarding app - Custom logs install Elastic Agent',
{ tag: ['@ess', '@svlOblt'] },
() => {
test.describe('Custom integration', () => {
test.beforeEach(setupPage({ isAdmin: true }));
test('Users should be able to see the custom integration success callout', async ({
pageObjects: { customLogsPage },
}) => {
await expect(customLogsPage.customIntegrationSuccessCallout).toBeVisible();
});
});
test.describe('ApiKey generation', () => {
test.describe('when user is missing privileges', () => {
test.beforeEach(setupPage({ isAdmin: false }));
test('apiKey is not generated', async ({ pageObjects: { customLogsPage } }) => {
await expect(customLogsPage.apiKeyPrivilegesErrorCallout).toBeVisible();
});
});
test.describe('when user has proper privileges', () => {
test.beforeEach(setupPage({ isAdmin: true }));
test('apiKey is generated', async ({ pageObjects: { customLogsPage } }) => {
await expect(customLogsPage.apiKeyCreateSuccessCallout).toBeVisible();
});
});
test.describe('when an error occurred on creation', () => {
test.beforeEach(async ({ browserAuth, pageObjects: { customLogsPage }, page }) => {
await page.route('**/observability_onboarding/logs/flow', (route) => {
route.fulfill({
status: 500,
body: JSON.stringify({
message: 'Internal error',
}),
});
});
});
test.beforeEach(setupPage({ isAdmin: true }));
test('apiKey is not generated', async ({ pageObjects: { customLogsPage } }) => {
await expect(customLogsPage.apiKeyCreateErrorCallout).toBeVisible();
});
});
});
test.describe('Install the Elastic Agent step', () => {
test.beforeEach(setupPage({ isAdmin: true }));
test.describe('When user select Linux OS', () => {
test('Auto download config to host is disabled by default', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.linuxCodeSnippetButton.click();
await expect(customLogsPage.autoDownloadConfigurationToggle).not.toBeChecked();
});
test('Installation script is shown', async ({ pageObjects: { customLogsPage } }) => {
await expect(customLogsPage.installCodeSnippet).toBeVisible();
});
});
test.describe('When user select Mac OS', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.macOSCodeSnippetButton.click();
});
test('Auto download config to host is disabled by default', async ({
pageObjects: { customLogsPage },
}) => {
await expect(customLogsPage.autoDownloadConfigurationToggle).not.toBeChecked();
});
test('Installation script is shown', async ({ pageObjects: { customLogsPage } }) => {
await expect(customLogsPage.installCodeSnippet).toBeVisible();
});
});
test.describe('When user select Windows OS', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.windowsCodeSnippetButton.click();
});
test('Auto download config to host is disabled by default', async ({
pageObjects: { customLogsPage },
}) => {
await expect(customLogsPage.autoDownloadConfigurationToggle).not.toBeChecked();
});
test('A link to the documentation is shown instead of installation script', async ({
pageObjects: { customLogsPage },
}) => {
await expect(customLogsPage.windowsInstallElasticAgentDocLink).toBeVisible();
await expect(customLogsPage.installCodeSnippet).not.toBeVisible();
});
});
test.describe('When Auto download config', () => {
test.describe('is selected', () => {
test('autoDownloadConfig flag is added to installation script', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.autoDownloadConfigurationToggle.click();
await expect(customLogsPage.autoDownloadConfigurationCallout).toBeVisible();
await expect(customLogsPage.installCodeSnippet).toContainText('autoDownloadConfig=1');
});
test('Download config button is disabled', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.autoDownloadConfigurationToggle.click();
await expect(customLogsPage.downloadConfigurationButton).toBeDisabled();
});
});
test('is not selected autoDownloadConfig flag is not added to installation script', async ({
pageObjects: { customLogsPage },
}) => {
await expect(customLogsPage.installCodeSnippet).not.toContainText('autoDownloadConfig=1');
});
});
test.describe('When user executes the installation script in the host', () => {
let onboardingId: string;
test.describe('updates on steps are shown in the flow', () => {
test.beforeEach(async ({ page, pageObjects: { customLogsPage } }) => {
await page.route('**/observability_onboarding/logs/flow', async (route) => {
const response = await route.fetch();
const body = await response.json();
onboardingId = body.onboardingId;
await route.fulfill({ response });
});
await expect(customLogsPage.apiKeyCreateSuccessCallout).toBeVisible();
});
test.describe('Download elastic Agent step', () => {
test('shows a loading callout when elastic agent is downloading', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'loading'
);
await expect(
customLogsPage.stepStatusLoading.getByText(
CustomLogsPage.ASSERTION_MESSAGES.DOWNLOADING_AGENT_STATUS
)
).toBeVisible();
});
test('shows a success callout when elastic agent is downloaded', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await expect(
customLogsPage.stepStatusComplete.getByText(
CustomLogsPage.ASSERTION_MESSAGES.DOWNLOADED_AGENT_STATUS
)
).toBeVisible();
});
test('shows a danger callout when elastic agent was not downloaded', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'danger'
);
await expect(
customLogsPage.stepStatusDanger.getByText(
CustomLogsPage.ASSERTION_MESSAGES.DOWNLOAD_AGENT_DANGER_CALLOUT
)
).toBeVisible();
});
});
test.describe('Extract elastic Agent step', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
});
test('shows a loading callout when elastic agent is extracting', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'loading'
);
await expect(
customLogsPage.stepStatusLoading.getByText(
CustomLogsPage.ASSERTION_MESSAGES.EXTRACTING_AGENT_STATUS
)
).toBeVisible();
});
test('shows a success callout when elastic agent is extracted', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
await expect(
customLogsPage.stepStatusComplete.getByText(
CustomLogsPage.ASSERTION_MESSAGES.EXTRACTED_AGENT_STATUS
)
).toBeVisible();
});
test('shows a danger callout when elastic agent was not extracted', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'danger'
);
await expect(
customLogsPage.stepStatusDanger.getByText(
CustomLogsPage.ASSERTION_MESSAGES.EXTRACT_AGENT_DANGER_CALLOUT
)
).toBeVisible();
});
});
test.describe('Install elastic Agent step', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
});
test('shows a loading callout when elastic agent is installing', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-install',
'loading'
);
await expect(
customLogsPage.stepStatusLoading.getByText(
CustomLogsPage.ASSERTION_MESSAGES.INSTALLING_AGENT_STATUS
)
).toBeVisible();
});
test('shows a success callout when elastic agent is installed', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-install',
'complete'
);
await expect(
customLogsPage.stepStatusComplete.getByText(
CustomLogsPage.ASSERTION_MESSAGES.INSTALLED_AGENT_STATUS
)
).toBeVisible();
});
test('shows a danger callout when elastic agent was not installed', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-install',
'danger'
);
await expect(
customLogsPage.stepStatusDanger.getByText(
CustomLogsPage.ASSERTION_MESSAGES.INSTALL_AGENT_DANGER_CALLOUT
)
).toBeVisible();
});
});
test.describe('Check elastic Agent status step', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-install',
'complete'
);
});
test('shows a loading callout when getting elastic agent status', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-status',
'loading'
);
await expect(
customLogsPage.stepStatusLoading.getByText(
CustomLogsPage.ASSERTION_MESSAGES.CONNECTING_TO_AGENT_STATUS
)
).toBeVisible();
});
test('shows a success callout when elastic agent status is healthy', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-status',
'complete',
{
agentId: 'test-agent-id',
}
);
await expect(
customLogsPage.stepStatusComplete.getByText(
CustomLogsPage.ASSERTION_MESSAGES.CONNECTED_TO_AGENT_STATUS
)
).toBeVisible();
});
test('shows a warning callout when elastic agent status is not healthy', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-status',
'warning'
);
await expect(
customLogsPage.stepStatusWarning.getByText(
CustomLogsPage.ASSERTION_MESSAGES.CONNECT_AGENT_WARNING_CALLOUT
)
).toBeVisible();
});
});
});
});
});
test.describe('Configure Elastic Agent step', () => {
let onboardingId: string;
test.beforeEach(setupPage({ isAdmin: true }));
test.beforeEach(async ({ page, pageObjects: { customLogsPage } }) => {
await page.route('**/observability_onboarding/logs/flow', async (route) => {
const response = await route.fetch();
const body = await response.json();
onboardingId = body.onboardingId;
await route.fulfill({ response });
});
await expect(customLogsPage.apiKeyCreateSuccessCallout).toBeVisible();
});
test.describe('When user select Linux OS', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.autoDownloadConfigurationToggle.click();
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete');
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete');
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete', {
agentId: 'test-agent-id',
});
});
test('shows loading callout when config is being downloaded to the host', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading');
await expect(
customLogsPage.stepStatusLoading.getByText(
CustomLogsPage.ASSERTION_MESSAGES.DOWNLOAD_AGENT_CONFIG_STATUS
)
).toBeVisible();
});
test('shows success callout when the configuration has been written to the host', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete');
await expect(
customLogsPage.stepStatusComplete.getByText(
CustomLogsPage.ASSERTION_MESSAGES.AGENT_CONFIGURATION_SUCCESS_CALLOUT_LINUX
)
).toBeVisible();
});
test('shows warning callout when the configuration was not written in the host', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-config', 'warning');
await expect(
customLogsPage.stepStatusWarning.getByText(
CustomLogsPage.ASSERTION_MESSAGES.CONFIGURE_AGENT_WARNING_CALLOUT
)
).toBeVisible();
});
});
test.describe('When user select Mac OS', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.macOSCodeSnippetButton.click();
await customLogsPage.autoDownloadConfigurationToggle.click();
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-extract', 'complete');
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-install', 'complete');
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-status', 'complete', {
agentId: 'test-agent-id',
});
});
test('shows loading callout when config is being downloaded to the host', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-config', 'loading');
await expect(
customLogsPage.stepStatusLoading.getByText(
CustomLogsPage.ASSERTION_MESSAGES.DOWNLOADING_AGENT_CONFIG_STATUS
)
).toBeVisible();
});
test('shows success callout when the configuration has been written to the host', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-config', 'complete');
await expect(
customLogsPage.stepStatusComplete.getByText(
CustomLogsPage.ASSERTION_MESSAGES.AGENT_CONFIGURATION_SUCCESS_CALLOUT_MACOS
)
).toBeVisible();
});
test('shows warning callout when the configuration was not written in the host', async ({
pageObjects: { customLogsPage },
}) => {
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-config', 'warning');
await expect(
customLogsPage.stepStatusWarning.getByText(
CustomLogsPage.ASSERTION_MESSAGES.CONFIGURE_AGENT_WARNING_CALLOUT
)
).toBeVisible();
});
});
test.describe('When user select Windows', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
customLogsPage.windowsCodeSnippetButton.click();
});
test('step is disabled', async ({ pageObjects: { customLogsPage } }) => {
await expect(
customLogsPage.configureElasticAgentStep.getByText(
CustomLogsPage.ASSERTION_MESSAGES.INSTALLATION_STEP_2_DISABLED
)
).toBeVisible();
});
});
});
test.describe('Check logs step', () => {
let onboardingId: string;
test.beforeEach(setupPage({ isAdmin: true }));
test.beforeEach(async ({ page, pageObjects: { customLogsPage } }) => {
await page.route('**/observability_onboarding/logs/flow', async (route) => {
const response = await route.fetch();
const body = await response.json();
onboardingId = body.onboardingId;
await route.fulfill({ response });
});
await expect(customLogsPage.apiKeyCreateSuccessCallout).toBeVisible();
});
test.describe('When user select Linux OS or MacOS', () => {
test.describe('When configure Elastic Agent step is not finished', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-install',
'complete'
);
await customLogsPage.updateInstallationStepStatus(onboardingId, 'ea-status', 'loading');
});
test('check logs is not triggered', async ({ page }) => {
await expect(
page.locator(
'[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-incomplete"]'
)
).toBeVisible();
await expect(
page.locator('.euiStep__title', { hasText: 'Ship logs to Elastic Observability' })
).toBeVisible();
});
});
test.describe('When configure Elastic Agent step has finished', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-download',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-extract',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-install',
'complete'
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-status',
'complete',
{
agentId: 'test-agent-id',
}
);
await customLogsPage.updateInstallationStepStatus(
onboardingId,
'ea-config',
'complete'
);
});
test('shows loading callout when logs are being checked', async ({ page }) => {
await expect(
page.locator(
'[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-loading"]'
)
).toBeVisible();
await expect(
page.locator('.euiStep__title', { hasText: 'Waiting for logs to be shipped...' })
).toBeVisible();
});
});
});
test.describe('When user select Windows', () => {
test.beforeEach(async ({ pageObjects: { customLogsPage } }) => {
await customLogsPage.windowsCodeSnippetButton.click();
});
test('step is disabled', async ({ pageObjects: { customLogsPage }, page }) => {
await expect(
page.locator(
'[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-disabled"]'
)
).toBeVisible();
});
});
});
test.describe('When logs are being shipped', () => {
test.beforeEach(async ({ page }) => {
await page.route('**/progress', (route) => {
route.fulfill({
status: 200,
body: JSON.stringify({
progress: {
'ea-download': { status: 'complete' },
'ea-extract': { status: 'complete' },
'ea-install': { status: 'complete' },
'ea-status': { status: 'complete' },
'ea-config': { status: 'complete' },
'logs-ingest': { status: 'complete' },
},
}),
});
});
});
test.beforeEach(setupPage({ isAdmin: true }));
test('shows success callout when logs have arrived to elastic', async ({ page }) => {
await expect(
page.locator(
'[data-test-subj="obltOnboardingCheckLogsStep"] .euiStep__titleWrapper [class$="euiStepNumber-s-complete"]'
)
).toBeVisible();
await expect(
page.locator('.euiStep__title', { hasText: 'Logs are being shipped!' })
).toBeVisible();
});
test('when user clicks on Explore Logs it navigates to observability Logs Explorer', async ({
page,
}) => {
await page.locator('[data-test-subj="obltOnboardingExploreLogs"]').click();
await expect(page).toHaveURL(/\/app\/observability-logs-explorer/);
});
});
}
);

View file

@ -7,7 +7,8 @@
"**/*"
],
"kbn_references": [
"@kbn/scout"
"@kbn/scout",
"@kbn/scout-oblt"
],
"exclude": [
"target/**/*"

View file

@ -7010,6 +7010,10 @@
version "0.0.0"
uid ""
"@kbn/scout-oblt@link:x-pack/solutions/observability/packages/kbn-scout-oblt":
version "0.0.0"
uid ""
"@kbn/scout-reporting@link:packages/kbn-scout-reporting":
version "0.0.0"
uid ""