Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Experiment] Use REST API in e2e tests to build up states #33414

Merged
merged 11 commits into from
Aug 12, 2021

Conversation

kevin940726
Copy link
Member

@kevin940726 kevin940726 commented Jul 14, 2021

Description

Use REST API to build up/clear states in e2e tests.

Why

Most of our e2e tests require setting up the states before actually running the tests. The way we currently do is to use puppeteer to manually visit those screens and click those buttons. It works fine but it's also very time-consuming. Before each test runs, we have to visit several screens and wait for them to finish, that's dozens of unnecessary and repetitive HTTP requests. What we should do is to build up the states programmatically to skip these steps. As popularized by Cypress (#ref-1, #ref-2), we can focus on what needs to be tested and stop wasting time on building up the states manually.

Benefits are:

  • Easier to read/write tests. Since we don't have to insert 100 different blocks before we can test the interactions between them, for instance.
  • Faster tests. It's quite obvious that we'll speed up tests if we skip some steps.

Why REST API

At first, WP-CLI is considered and seemed like the perfect choice for the job. However, after testing it in some tasks, it seems to be too slow when running some basic tasks. For instance, running a simple list method to get all the saved widgets takes about 0.5 to 1 second to run. In fact, all commands take at least 0.5 to 1 second to run. I'm not sure if this is the limitation of the framework, but seems like the bottleneck here is not solvable at the time.

The next option would be to use the REST API. The same task that took WP-CLI 1 second can now be done under 100ms instead. Another benefit of using REST API is that we don't need to install anything before we can use it. We can run those tests on any WordPress instance as long as they have REST API supported.

There are disadvantages too though. Some tasks cannot be done with REST API, such as activating themes and plugins. We might want to use WP-CLI for those tasks, or call the internal API with auth cookie instead. I'm still in between of different solutions.

How it works

This PR tries to migrate some tasks in the widgets e2e tests to use the REST API, which includes:

  1. Delete all widgets
  2. Get all widgets
  3. Insert a legacy widget

We can reuse the @wordpress/api-fetch package that we've already been using in the frontend, steadier learning curve. api-fetch only supports browser environments though. Fortunately, we can workaround that by patching window.fetch to node-fetch.

Some REST API require authentication too. The solution is to add a basic-auth test plugin to enable HTTP basic authentication on every REST API endpoint. This test plugin should only be used in test environments.

Then, we wrap apiFetch with the authentication token in a test utility rest, which combines both to make the REST requests. Finally, all we left is to implement those requests in our tests. It looks something like this:

import { __experimentalRest as rest } from '@wordpress/e2e-test-utils';

await rest({ path: '/wp/v2/widgets' }); // Get all the widgets.

There's also a batch util we can use to send batched requests.

import { __experimentalBatch as batch } from '@wordpress/e2e-test-utils';

await batch(requests); // `requests` is an array of requests.

Result

Tests trunk Now
customizing-widgets.test.js 153s 122s
editing-widgets.test.js 136s 122s

The tests are being run locally on macOS. The results seem to have significant deviation between runs though.

How has this been tested?

E2E Tests should pass

Types of changes

New feature

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).

@kevin940726 kevin940726 added [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible [Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests. labels Jul 14, 2021
@github-actions
Copy link

github-actions bot commented Jul 14, 2021

Size Change: -1.22 kB (0%)

Total Size: 1.03 MB

Filename Size Change
build/block-editor/index.min.js 118 kB +43 B (0%)
build/block-library/blocks/columns/editor-rtl.css 194 B +5 B (+3%)
build/block-library/blocks/columns/editor.css 193 B +5 B (+3%)
build/block-library/blocks/search/editor-rtl.css 165 B -44 B (-21%) 🎉
build/block-library/blocks/search/editor.css 165 B -44 B (-21%) 🎉
build/block-library/blocks/search/style-rtl.css 374 B +6 B (+2%)
build/block-library/blocks/search/style.css 375 B +3 B (+1%)
build/block-library/common-rtl.css 832 B -458 B (-36%) 🎉
build/block-library/common.css 830 B -458 B (-36%) 🎉
build/block-library/editor-rtl.css 9.38 kB -505 B (-5%)
build/block-library/editor.css 9.37 kB -502 B (-5%)
build/block-library/index.min.js 146 kB +316 B (0%)
build/block-library/style-rtl.css 9.78 kB -462 B (-5%)
build/block-library/style.css 9.79 kB -462 B (-5%)
build/blocks/index.min.js 47 kB +179 B (0%)
build/components/index.min.js 209 kB +1 kB (0%)
build/components/style-rtl.css 15.7 kB -16 B (0%)
build/components/style.css 15.8 kB -17 B (0%)
build/edit-post/index.min.js 28.4 kB +53 B (0%)
build/edit-site/index.min.js 25.8 kB +134 B (+1%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 931 B
build/admin-manifest/index.min.js 1.09 kB
build/annotations/index.min.js 2.7 kB
build/api-fetch/index.min.js 2.19 kB
build/autop/index.min.js 2.08 kB
build/blob/index.min.js 459 B
build/block-directory/index.min.js 6.21 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/style-rtl.css 13.9 kB
build/block-editor/style.css 13.9 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 58 B
build/block-library/blocks/audio/editor.css 58 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 474 B
build/block-library/blocks/button/editor.css 474 B
build/block-library/blocks/button/style-rtl.css 605 B
build/block-library/blocks/button/style.css 604 B
build/block-library/blocks/buttons/editor-rtl.css 315 B
build/block-library/blocks/buttons/editor.css 315 B
build/block-library/blocks/buttons/style-rtl.css 370 B
build/block-library/blocks/buttons/style.css 370 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 90 B
build/block-library/blocks/code/style.css 90 B
build/block-library/blocks/code/theme-rtl.css 131 B
build/block-library/blocks/code/theme.css 131 B
build/block-library/blocks/columns/style-rtl.css 474 B
build/block-library/blocks/columns/style.css 475 B
build/block-library/blocks/cover/editor-rtl.css 666 B
build/block-library/blocks/cover/editor.css 670 B
build/block-library/blocks/cover/style-rtl.css 1.23 kB
build/block-library/blocks/cover/style.css 1.23 kB
build/block-library/blocks/embed/editor-rtl.css 488 B
build/block-library/blocks/embed/editor.css 488 B
build/block-library/blocks/embed/style-rtl.css 400 B
build/block-library/blocks/embed/style.css 400 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 322 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 707 B
build/block-library/blocks/gallery/editor.css 706 B
build/block-library/blocks/gallery/style-rtl.css 1.05 kB
build/block-library/blocks/gallery/style.css 1.05 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 93 B
build/block-library/blocks/group/theme.css 93 B
build/block-library/blocks/heading/editor-rtl.css 152 B
build/block-library/blocks/heading/editor.css 152 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/home-link/style-rtl.css 247 B
build/block-library/blocks/home-link/style.css 247 B
build/block-library/blocks/html/editor-rtl.css 283 B
build/block-library/blocks/html/editor.css 284 B
build/block-library/blocks/image/editor-rtl.css 728 B
build/block-library/blocks/image/editor.css 728 B
build/block-library/blocks/image/style-rtl.css 482 B
build/block-library/blocks/image/style.css 487 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B
build/block-library/blocks/latest-posts/editor.css 137 B
build/block-library/blocks/latest-posts/style-rtl.css 528 B
build/block-library/blocks/latest-posts/style.css 527 B
build/block-library/blocks/list/style-rtl.css 63 B
build/block-library/blocks/list/style.css 63 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 488 B
build/block-library/blocks/media-text/style.css 485 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 474 B
build/block-library/blocks/navigation-link/editor.css 474 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation/editor-rtl.css 1.69 kB
build/block-library/blocks/navigation/editor.css 1.69 kB
build/block-library/blocks/navigation/style-rtl.css 1.65 kB
build/block-library/blocks/navigation/style.css 1.64 kB
build/block-library/blocks/navigation/view.min.js 2.52 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 310 B
build/block-library/blocks/page-list/editor.css 310 B
build/block-library/blocks/page-list/style-rtl.css 242 B
build/block-library/blocks/page-list/style.css 242 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 248 B
build/block-library/blocks/paragraph/style.css 248 B
build/block-library/blocks/post-author/editor-rtl.css 210 B
build/block-library/blocks/post-author/editor.css 210 B
build/block-library/blocks/post-author/style-rtl.css 182 B
build/block-library/blocks/post-author/style.css 181 B
build/block-library/blocks/post-comments-form/style-rtl.css 140 B
build/block-library/blocks/post-comments-form/style.css 140 B
build/block-library/blocks/post-comments/style-rtl.css 360 B
build/block-library/blocks/post-comments/style.css 359 B
build/block-library/blocks/post-content/editor-rtl.css 138 B
build/block-library/blocks/post-content/editor.css 138 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 412 B
build/block-library/blocks/post-featured-image/editor.css 412 B
build/block-library/blocks/post-featured-image/style-rtl.css 143 B
build/block-library/blocks/post-featured-image/style.css 143 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 378 B
build/block-library/blocks/post-template/style.css 379 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 60 B
build/block-library/blocks/post-title/style.css 60 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 361 B
build/block-library/blocks/pullquote/style.css 360 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 270 B
build/block-library/blocks/query-pagination/editor.css 262 B
build/block-library/blocks/query-pagination/style-rtl.css 168 B
build/block-library/blocks/query-pagination/style.css 168 B
build/block-library/blocks/query-title/editor-rtl.css 85 B
build/block-library/blocks/query-title/editor.css 85 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 169 B
build/block-library/blocks/quote/style.css 169 B
build/block-library/blocks/quote/theme-rtl.css 220 B
build/block-library/blocks/quote/theme.css 222 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 250 B
build/block-library/blocks/separator/style.css 250 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 462 B
build/block-library/blocks/site-logo/editor.css 464 B
build/block-library/blocks/site-logo/style-rtl.css 153 B
build/block-library/blocks/site-logo/style.css 153 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 165 B
build/block-library/blocks/social-link/editor.css 165 B
build/block-library/blocks/social-links/editor-rtl.css 812 B
build/block-library/blocks/social-links/editor.css 811 B
build/block-library/blocks/social-links/style-rtl.css 1.33 kB
build/block-library/blocks/social-links/style.css 1.33 kB
build/block-library/blocks/spacer/editor-rtl.css 307 B
build/block-library/blocks/spacer/editor.css 307 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 146 B
build/block-library/blocks/tag-cloud/style.css 146 B
build/block-library/blocks/template-part/editor-rtl.css 636 B
build/block-library/blocks/template-part/editor.css 635 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/term-description/editor-rtl.css 90 B
build/block-library/blocks/term-description/editor.css 90 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/reset-rtl.css 527 B
build/block-library/reset.css 527 B
build/block-library/theme-rtl.css 688 B
build/block-library/theme.css 692 B
build/block-serialization-default-parser/index.min.js 1.09 kB
build/block-serialization-spec-parser/index.min.js 2.79 kB
build/compose/index.min.js 10.2 kB
build/core-data/index.min.js 12.3 kB
build/customize-widgets/index.min.js 10.4 kB
build/customize-widgets/style-rtl.css 1.5 kB
build/customize-widgets/style.css 1.49 kB
build/data-controls/index.min.js 614 B
build/data/index.min.js 7.03 kB
build/date/index.min.js 31.5 kB
build/deprecated/index.min.js 428 B
build/dom-ready/index.min.js 304 B
build/dom/index.min.js 4.53 kB
build/edit-navigation/index.min.js 13.4 kB
build/edit-navigation/style-rtl.css 3.1 kB
build/edit-navigation/style.css 3.1 kB
build/edit-post/classic-rtl.css 492 B
build/edit-post/classic.css 494 B
build/edit-post/style-rtl.css 7.18 kB
build/edit-post/style.css 7.17 kB
build/edit-site/style-rtl.css 5.01 kB
build/edit-site/style.css 5.01 kB
build/edit-widgets/index.min.js 15.9 kB
build/edit-widgets/style-rtl.css 4.01 kB
build/edit-widgets/style.css 4.02 kB
build/editor/index.min.js 37.5 kB
build/editor/style-rtl.css 3.92 kB
build/editor/style.css 3.91 kB
build/element/index.min.js 3.16 kB
build/escape-html/index.min.js 517 B
build/format-library/index.min.js 5.36 kB
build/format-library/style-rtl.css 668 B
build/format-library/style.css 669 B
build/hooks/index.min.js 1.55 kB
build/html-entities/index.min.js 424 B
build/i18n/index.min.js 3.59 kB
build/is-shallow-equal/index.min.js 501 B
build/keyboard-shortcuts/index.min.js 1.49 kB
build/keycodes/index.min.js 1.25 kB
build/list-reusable-blocks/index.min.js 1.85 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.88 kB
build/notices/index.min.js 845 B
build/nux/index.min.js 2.03 kB
build/nux/style-rtl.css 747 B
build/nux/style.css 743 B
build/plugins/index.min.js 1.83 kB
build/primitives/index.min.js 921 B
build/priority-queue/index.min.js 582 B
build/react-i18n/index.min.js 671 B
build/redux-routine/index.min.js 2.63 kB
build/reusable-blocks/index.min.js 2.28 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.5 kB
build/server-side-render/index.min.js 1.32 kB
build/shortcode/index.min.js 1.48 kB
build/token-list/index.min.js 562 B
build/url/index.min.js 1.72 kB
build/viewport/index.min.js 1.02 kB
build/warning/index.min.js 248 B
build/widgets/index.min.js 6.27 kB
build/widgets/style-rtl.css 1.04 kB
build/widgets/style.css 1.04 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

talldan
talldan previously approved these changes Jul 15, 2021
Copy link
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'll be really needed as tests start to involve much more than just posts and blocks. I know the navigation editor/block tests could definitely use this too.

Might want to gather a few more opinions/reviews before merging, but this looks good to me as a first step 👍

@gziolo
Copy link
Member

gziolo commented Jul 15, 2021

Have you tried creating a WP plugin that sets the initial state by executing DB calls instead of using REST API? Well, it could as well run the same REST API calls inside PHP for start.

@noisysocks
Copy link
Member

Have you tried creating a WP plugin that sets the initial state by executing DB calls instead of using REST API? Well, it could as well run the same REST API calls inside PHP for start.

This is a good idea, but how would Puppeteer trigger the PHP code? It would have to make a page request (e.g. navigate to /wp-admin?gutenerg_reset_test_state=1) which the plugin intercepts. We'd only be saving one or two requests since the REST API supports batching.

@kevin940726
Copy link
Member Author

Though not related, changing the implementation could probably fix #33275 🤞. If that's the case then we'll have a better understanding of the underlying issue.

@kevin940726
Copy link
Member Author

Have you tried creating a WP plugin that sets the initial state by executing DB calls instead of using REST API?

Hmm, I'm not sure I understand.

Are DB calls something like SQL commands? I've considered this, but the DB structure in WP is often very complicated and not recommended to be altered directly.

If it's something like calling core's PHP functions then I think that have the same effects of calling the REST API? Some core functions are not exposed to REST API though (activating themes/plugins for instance), then I think it would be a good alternative for them.

@gziolo
Copy link
Member

gziolo commented Jul 16, 2021

This is a good idea, but how would Puppeteer trigger the PHP code? It would have to make a page request (e.g. navigate to /wp-admin?gutenerg_reset_test_state=1) which the plugin intercepts. We'd only be saving one or two requests since the REST API supports batching.

With WP hooks you don't need to navigate to a different page, the plugin can just run the necessary logic when loading the page where you are going to do all the testing. Another difference is that you would load the page with all REST API (or their corresponding WP function wrappers) calls executed so you would only have to run test spec because the initial conditions are already set - no need to do a page reload as well like in Should display legacy widgets test.

Are DB calls something like SQL commands? I've considered this, but the DB structure in WP is often very complicated and not recommended to be altered directly.

I didn't mean literally SQL commands. Whatever is the most appropriate for your use case. If that is a single WP function to call then it's a win.

@noisysocks
Copy link
Member

With WP hooks you don't need to navigate to a different page, the plugin can just run the necessary logic when loading the page where you are going to do all the testing.

Right, but how would we distinguish between a page load that happens at the beginning of a test versus one that happens mid-test?

@gziolo
Copy link
Member

gziolo commented Jul 19, 2021

With WP hooks you don't need to navigate to a different page, the plugin can just run the necessary logic when loading the page where you are going to do all the testing.

Right, but how would we distinguish between a page load that happens at the beginning of a test versus one that happens mid-test?

One way I can think of is by checking whether a special flag is set in $_GET. Then you can do either an HTTP redirect in PHP to an URL without the flag, or remove this flag in JavaScript to avoid executing the same logic when you refresh the page in some of the existing tests.

@kevin940726
Copy link
Member Author

Another difference is that you would load the page with all REST API (or their corresponding WP function wrappers) calls executed so you would only have to run test spec because the initial conditions are already set - no need to do a page reload as well like in Should display legacy widgets test.

The page reload is needed because the test happens after we goto the widgets screen. We can also call rest() before visiting the widgets screen to reset the states, but that would require refactoring beforeEach. Reloading is just a simpler method and doesn't really impact performance that much. Even if we use the WP hook approach, we would still need to reload the page in test.

@kevin940726
Copy link
Member Author

I also refactored activatePlugin and deactivatePlugin to use REST APIs (thanks to @Mamaduka for the heads-up!).

@kevin940726
Copy link
Member Author

I opened #33720 as an experiment of something similar to @gziolo's approach, evaluating WordPress commands directly via REST API. Currently, there's only one use case which is to switch between themes. It feels like an overkill to try to introduce something so powerful in our testing toolchain.

I prefer using REST API to reset/build states over calling WP commands, as they are well-documented and match more closely to what users would do. We can even think of them as e2e tests for REST APIs in a way.

In dea992c, I also fixed an issue where performance tests were always using the latest e2e-test-utils which breaks whenever we update the package in PRs.

I think this PR is the way forward, unless there're major issues I would like to merge it and monitor it in trunk. WDYT?

@gziolo
Copy link
Member

gziolo commented Jul 29, 2021

I opened #33720 as an experiment of something similar to @gziolo's approach, evaluating WordPress commands directly via REST API. Currently, there's only one use case which is to switch between themes. It feels like an overkill to try to introduce something so powerful in our testing toolchain.

I don't think it's what I had in mind, because it's using REST API to send PHP code to run through eval.

The alternative I considered was a plugin that is always installed in E2E tests' context and it intercepts a special param in the URL. Based on the values passed in this param, we could execute some predefined commands that have access to the full WordPress API. It would be more useful for resetting the WordPress state before all test runs, think about this code that is handled now through Puppeteer:

await activateTheme( 'twentytwentyone' );
await trashAllPosts();
await trashAllPosts( 'wp_block' );
await setupBrowser();
await activatePlugin( 'gutenberg-test-plugin-disables-the-css-animations' );

@kevin940726
Copy link
Member Author

Based on the values passed in this param, we could execute some predefined commands that have access to the full WordPress API.

This would work too! But I would like to avoid requiring developers to know PHP while all they want to do is to write e2e tests. REST API is already an officially supported interface that we can use, and the rest helpers is essentially just an apiFetch clone too. In addition, if we can't do something via REST API, like switching themes, then mostly likely we would also want to implement that in the future too.

await activateTheme( 'twentytwentyone' );
await trashAllPosts();
await trashAllPosts( 'wp_block' );
await setupBrowser();
await activatePlugin( 'gutenberg-test-plugin-disables-the-css-animations' );

I believe most of them can also be implemented using REST API, and we don't have to deprecate existing test utils to achieve that. Just like activatePlugin/deactivatePlugin refactor in this PR, the change could be seamless and mostly backward-compatible. The downside are that it's slightly more code and require more RTT, I don't think they are big deals though, calling several requests in sequence is still faster than manually visiting the pages.

Copy link
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still looks good to me, thanks for working on this.

Maybe test that it definitely works in Windows before merging.

bin/plugin/commands/performance.js Outdated Show resolved Hide resolved
packages/e2e-test-utils/src/plugins.js Outdated Show resolved Hide resolved
}

await deactivatePlugin( 'gutenberg-test-classic-widgets' );
await batch(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be batched with the previous batch, or are there concerns about limits? (I think the limit is 25 or something). The limit might actually be a concern here if a test ever adds lots of widgets.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually can't. I think it might have some race condition going on in those API, I didn't look into why though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok 👍

Might still be worth looking into the batch limit thing (some background - #28709) as a follow-up, I think this batch function should ideally split the requests array based on the limit when it's over the limit and then make sequential requests for each part.

Comment on lines +627 to +634
await rest( {
method: 'POST',
path: '/wp/v2/widgets',
data: {
id_base: 'search',
sidebar: 'sidebar-1',
instance: defaultSearchInstance,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like over time it'd be nice to see some simple helper like await createWidgets( widgets, sidebarId ), but this can always evolve over time with rest and batch as the foundation.

packages/e2e-test-utils/src/rest-api.js Outdated Show resolved Hide resolved
@kevin940726 kevin940726 merged commit 7d8e740 into trunk Aug 12, 2021
@kevin940726 kevin940726 deleted the try/rest-api-e2e-utils branch August 12, 2021 08:43
@github-actions github-actions bot added this to the Gutenberg 11.4 milestone Aug 12, 2021
@youknowriad
Copy link
Contributor

Is this new ENVIRONMENT_DIRECTORY env variable mandatory to run e2e tests and performance tests? What's its purpose?

module.exports = {
...require( '@wordpress/scripts/config/jest-e2e.config' ),
testMatch: [ '**/performance/*.test.js' ],
setupFiles: [ '<rootDir>/config/gutenberg-phase.js' ],
moduleNameMapper: {
// Use different versions of e2e-test-utils for different environments
// rather than always using the latest.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To use the PR's version of e2e-test-utils so that changes in that package can be picked up when running the performance tests. This is not needed anymore though since we made the changes opt-in and experimental now. Just forgot to revert it 😅.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm wrong, this was already the case, PRs use the PR's version of the e2e-test-utils always. There's already a --tests-env option in the performance PR?

! spinner.classList.contains( 'is-active' )
);
// Create a search widget in the first sidebar using the default instance.
await rest( {
Copy link
Contributor

@youknowriad youknowriad Aug 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests seem less readable with all these inline rest calls. I believe you should consider hiding them being some utils as otherwise, it's not really clear what the tests are doing unless you wrote it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it has been brought up here: #33414 (comment).

// Only need to set them once but before any requests.
await Promise.all( [ setAPIRootURL, setNonce ] );

return await apiFetch( options );
Copy link
Member

@gziolo gziolo Aug 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use wp.apiFetch instead that should have already all params set when the user is logged in. Something like this:

await page.evaluate( ( params ) =>
	wp.apiFetch( params ),
	options
);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make the test depends on the page it's currently running on. Some pages don't have wp.apiFetch available by default, and sometimes the logged in user doesn't have the necessary permissions. It also means that a reload is required after making such requests. Calling it server-side seems like a more robust approach to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must admit that it's all confusing. In the current shape, the rest method is doing a REST API request as an admin with hardcoded credentials to admin + password. If the goal is to make it general purpose then it's far from it. Some concerns:

  • e2e tests can be executed also with sites that don't have the admin account with the default credentials
  • should it be possible to use rest with a different user account or as logged out?
  • wp-login.php set the cookie so it's very likely that once the test refreshes the page then the rest of the test is going to be performed as an admin

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rest isn't the best name here. It's running in the nodejs process rather than the browser context, so it's independent to the test and won't affect the current logged in user. As mentioned in the description of the PR, it should only be used to setup/clear states between or during tests. I'll keep searching for a better name here, fortunately it's still being marked as experimental.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests. [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants