playwright-ruby-client 0.6.0 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/README.md +33 -0
  3. data/documentation/babel.config.js +3 -0
  4. data/documentation/docs/api/accessibility.md +7 -0
  5. data/documentation/docs/api/browser.md +188 -0
  6. data/documentation/docs/api/browser_context.md +397 -0
  7. data/documentation/docs/api/browser_type.md +158 -0
  8. data/documentation/docs/api/cdp_session.md +7 -0
  9. data/documentation/docs/api/console_message.md +41 -0
  10. data/documentation/docs/api/dialog.md +71 -0
  11. data/documentation/docs/api/element_handle.md +618 -0
  12. data/documentation/docs/api/experimental/_category_.yml +3 -0
  13. data/documentation/docs/api/experimental/android.md +26 -0
  14. data/documentation/docs/api/experimental/android_device.md +92 -0
  15. data/documentation/docs/api/experimental/android_input.md +38 -0
  16. data/documentation/docs/api/experimental/android_socket.md +7 -0
  17. data/documentation/docs/api/experimental/android_web_view.md +7 -0
  18. data/documentation/docs/api/file_chooser.md +50 -0
  19. data/documentation/docs/api/frame.md +866 -0
  20. data/documentation/docs/api/js_handle.md +113 -0
  21. data/documentation/docs/api/keyboard.md +157 -0
  22. data/documentation/docs/api/mouse.md +69 -0
  23. data/documentation/docs/api/page.md +1402 -0
  24. data/documentation/docs/api/playwright.md +63 -0
  25. data/documentation/docs/api/request.md +188 -0
  26. data/documentation/docs/api/response.md +97 -0
  27. data/documentation/docs/api/route.md +79 -0
  28. data/documentation/docs/api/selectors.md +23 -0
  29. data/documentation/docs/api/touchscreen.md +8 -0
  30. data/documentation/docs/api/tracing.md +47 -0
  31. data/documentation/docs/api/web_socket.md +7 -0
  32. data/documentation/docs/api/worker.md +24 -0
  33. data/documentation/docs/article/api_coverage.mdx +11 -0
  34. data/documentation/docs/article/getting_started.md +152 -0
  35. data/documentation/docs/article/guides/_category_.yml +3 -0
  36. data/documentation/docs/article/guides/download_playwright_driver.md +49 -0
  37. data/documentation/docs/article/guides/launch_browser.md +119 -0
  38. data/documentation/docs/article/guides/rails_integration.md +205 -0
  39. data/documentation/docs/article/guides/recording_video.md +79 -0
  40. data/{docs → documentation/docs/include}/api_coverage.md +4 -3
  41. data/documentation/docusaurus.config.js +107 -0
  42. data/documentation/package.json +39 -0
  43. data/documentation/sidebars.js +15 -0
  44. data/documentation/src/components/HomepageFeatures.js +61 -0
  45. data/documentation/src/components/HomepageFeatures.module.css +13 -0
  46. data/documentation/src/css/custom.css +44 -0
  47. data/documentation/src/pages/index.js +50 -0
  48. data/documentation/src/pages/index.module.css +41 -0
  49. data/documentation/src/pages/markdown-page.md +7 -0
  50. data/documentation/static/.nojekyll +0 -0
  51. data/documentation/static/img/playwright-logo.svg +9 -0
  52. data/documentation/static/img/undraw_dropdown_menu.svg +1 -0
  53. data/documentation/static/img/undraw_web_development.svg +1 -0
  54. data/documentation/static/img/undraw_windows.svg +1 -0
  55. data/documentation/yarn.lock +8785 -0
  56. data/lib/playwright/channel_owners/binding_call.rb +33 -0
  57. data/lib/playwright/channel_owners/browser.rb +15 -27
  58. data/lib/playwright/channel_owners/browser_context.rb +15 -7
  59. data/lib/playwright/channel_owners/browser_type.rb +23 -8
  60. data/lib/playwright/channel_owners/element_handle.rb +2 -10
  61. data/lib/playwright/channel_owners/frame.rb +6 -28
  62. data/lib/playwright/channel_owners/js_handle.rb +2 -10
  63. data/lib/playwright/channel_owners/page.rb +18 -8
  64. data/lib/playwright/channel_owners/worker.rb +4 -0
  65. data/lib/playwright/input_files.rb +0 -8
  66. data/lib/playwright/javascript.rb +0 -10
  67. data/lib/playwright/javascript/expression.rb +2 -7
  68. data/lib/playwright/playwright_api.rb +16 -1
  69. data/lib/playwright/tracing_impl.rb +9 -9
  70. data/lib/playwright/version.rb +1 -1
  71. data/lib/playwright_api/accessibility.rb +7 -89
  72. data/lib/playwright_api/android.rb +10 -66
  73. data/lib/playwright_api/android_device.rb +10 -9
  74. data/lib/playwright_api/browser.rb +21 -172
  75. data/lib/playwright_api/browser_context.rb +54 -617
  76. data/lib/playwright_api/browser_type.rb +20 -108
  77. data/lib/playwright_api/cdp_session.rb +2 -25
  78. data/lib/playwright_api/console_message.rb +6 -6
  79. data/lib/playwright_api/dialog.rb +11 -92
  80. data/lib/playwright_api/element_handle.rb +60 -362
  81. data/lib/playwright_api/file_chooser.rb +0 -28
  82. data/lib/playwright_api/frame.rb +74 -713
  83. data/lib/playwright_api/js_handle.rb +16 -90
  84. data/lib/playwright_api/keyboard.rb +21 -213
  85. data/lib/playwright_api/mouse.rb +1 -45
  86. data/lib/playwright_api/page.rb +181 -1647
  87. data/lib/playwright_api/playwright.rb +14 -117
  88. data/lib/playwright_api/request.rb +15 -121
  89. data/lib/playwright_api/response.rb +7 -7
  90. data/lib/playwright_api/route.rb +8 -105
  91. data/lib/playwright_api/selectors.rb +6 -97
  92. data/lib/playwright_api/tracing.rb +7 -73
  93. data/lib/playwright_api/web_socket.rb +1 -1
  94. data/lib/playwright_api/worker.rb +28 -42
  95. metadata +57 -4
  96. data/lib/playwright/javascript/function.rb +0 -67
@@ -0,0 +1,79 @@
1
+ ---
2
+ sidebar_position: 4
3
+ ---
4
+
5
+ # Recording video
6
+
7
+ Playwright allows us to record the browser screen during automation.
8
+ https://playwright.dev/docs/videos
9
+
10
+ With this awesome feature, NO NEED to keep our attention focused on the screen during the automation :)
11
+
12
+ ```ruby {7,11-12,15-16}
13
+ require 'tmpdir'
14
+
15
+ playwright.chromium.launch do |browser|
16
+ Dir.mktmpdir do |tmp|
17
+ video_path = nil
18
+
19
+ browser.new_context(record_video_dir: tmp) do |context|
20
+ page = context.new_page
21
+ # play with page
22
+
23
+ # NOTE: Page#video is available **only when browser context is alive.**
24
+ video_path = page.video.path
25
+ end
26
+
27
+ # NOTE: video is completely saved **only after browser context is closed.**
28
+ handle_video_as_you_like(video_path)
29
+ end
30
+ ```
31
+
32
+ ## Specify where to put videos
33
+
34
+ Playwright puts videos on the directory specified at `record_video_dir`.
35
+
36
+ The previous example uses [Dir#mktmpdir](https://docs.ruby-lang.org/ja/latest/method/Dir/s/mktmpdir.html) for storing videos into a temprary directory. Also we simply specify a relative or absolute path like `./my_videos/` or `/path/to/videos`.
37
+
38
+ ## Getting video path and recorded video
39
+
40
+ This is really confising for beginners, but in Playwright
41
+
42
+ * We can get the video path **only when page is alive (before calling BrowserContext#close or Page#close)**
43
+ * We can acquire the completely saved video **only after calling BrowserContext#close**
44
+
45
+ So in most case, we have to store the video path in advance, and handle the saved video after BrowserContext is closed, as is shown the previous example code.
46
+
47
+ ### Using `video#save_as(path)`
48
+
49
+ If you want to just save video to somewhere without handling the video using `File.open`, you can simply use `video#save_as(path_to_save)`.
50
+
51
+ ```ruby {5,8,12-13}
52
+ require 'tmpdir'
53
+
54
+ playwright.chromium.launch do |browser|
55
+ Dir.mktmpdir do |tmp|
56
+ page = nil
57
+
58
+ browser.new_context(record_video_dir: tmp) do |context|
59
+ page = context.new_page
60
+ # play with page
61
+ end
62
+
63
+ # NOTE: video is completely saved **only after browser context is closed.**
64
+ page.video.save_as('my-important-video.webm')
65
+ end
66
+ ```
67
+
68
+ ## Using screen recording from Capybara driver
69
+
70
+ capybara-playwright-driver exposes a function to store the video.
71
+
72
+ ```ruby
73
+ Capybara.current_session.driver.on_save_screenrecord do |video_path|
74
+ # Handling recorded video here.
75
+ # video_path is like '/var/folders/xx/xxxxxxxxxx_xxxxxxxx/T/xxxxxxx-xxxxx-xxxxxxxx/e6bde41c5d05b2a02344b058bf1bfea2.webm'
76
+ end
77
+ ```
78
+
79
+ With this callback registration, we can record the videos without specifying `record_video_dir` explicitly or preparing a temporary directory. capybara-playwright-driver automatically prepare and set `record_video_dir` internally.
@@ -175,7 +175,7 @@
175
175
  * ~~wait_for_timeout~~
176
176
  * wait_for_url
177
177
 
178
- ## ~~Worker~~
178
+ ## Worker
179
179
 
180
180
  * ~~evaluate~~
181
181
  * ~~evaluate_handle~~
@@ -273,10 +273,12 @@
273
273
  * expect_navigation
274
274
  * expect_popup
275
275
  * expect_request
276
+ * ~~expect_request_finished~~
276
277
  * expect_response
277
278
  * wait_for_selector
278
279
  * ~~wait_for_timeout~~
279
280
  * wait_for_url
281
+ * ~~expect_websocket~~
280
282
  * ~~expect_worker~~
281
283
  * ~~workers~~
282
284
  * ~~wait_for_event~~
@@ -338,7 +340,7 @@
338
340
  * connect_over_cdp
339
341
  * executable_path
340
342
  * launch
341
- * ~~launch_persistent_context~~
343
+ * launch_persistent_context
342
344
  * name
343
345
 
344
346
  ## Playwright
@@ -352,7 +354,6 @@
352
354
 
353
355
  ## Tracing
354
356
 
355
- * export
356
357
  * start
357
358
  * stop
358
359
 
@@ -0,0 +1,107 @@
1
+ /** @type {import('@docusaurus/types').DocusaurusConfig} */
2
+ module.exports = {
3
+ title: 'playwright-ruby-client',
4
+ tagline: 'Playwright client library for Ruby',
5
+ url: 'https://playwright-ruby-client.vercel.app',
6
+ baseUrl: '/',
7
+ onBrokenLinks: 'throw',
8
+ onBrokenMarkdownLinks: 'warn',
9
+ favicon: 'img/playwright-logo.svg',
10
+ organizationName: 'YusukeIwaki', // Usually your GitHub org/user name.
11
+ projectName: 'playwright-ruby-client', // Usually your repo name.
12
+ themeConfig: {
13
+ navbar: {
14
+ title: 'playwright-ruby-client',
15
+ logo: {
16
+ alt: 'Playwright',
17
+ src: 'img/playwright-logo.svg',
18
+ },
19
+ items: [
20
+ {
21
+ type: 'doc',
22
+ docId: 'article/getting_started',
23
+ position: 'left',
24
+ label: 'Docs',
25
+ },
26
+ {
27
+ type: 'doc',
28
+ docId: 'api/playwright',
29
+ position: 'left',
30
+ label: 'API',
31
+ },
32
+ {
33
+ href: 'https://github.com/YusukeIwaki/playwright-ruby-client',
34
+ 'aria-label': 'GitHub',
35
+ className: 'header-github-link',
36
+ position: 'right',
37
+ },
38
+ ],
39
+ },
40
+ footer: {
41
+ style: 'dark',
42
+ links: [
43
+ {
44
+ title: 'Docs',
45
+ items: [
46
+ {
47
+ label: 'Getting started',
48
+ to: '/docs/article/getting_started',
49
+ },
50
+ {
51
+ label: 'API reference',
52
+ to: '/docs/api/playwright',
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ title: 'Source codes',
58
+ items: [
59
+ {
60
+ label: 'playwright-ruby-client',
61
+ to: 'https://github.com/YusukeIwaki/playwright-ruby-client',
62
+ },
63
+ {
64
+ label: 'capybara-playwright-driver',
65
+ to: 'https://github.com/YusukeIwaki/capybara-playwright-driver',
66
+ },
67
+ ],
68
+ },
69
+ {
70
+ title: 'HowTo',
71
+ items: [
72
+ {
73
+ label: 'Develop Playwright driver for Ruby',
74
+ to: 'https://yusukeiwaki.hatenablog.com/entry/2021/01/13/how-to-create-playwright-ruby-client',
75
+ },
76
+ {
77
+ label: 'Implement your own Capybara driver',
78
+ to: 'https://zenn.dev/yusukeiwaki/scraps/280aabf289ae29',
79
+ },
80
+ ],
81
+ },
82
+ ],
83
+ copyright: `Copyright © ${new Date().getFullYear()} @YusukeIwaki. <p>Built with <a href="https://v2.docusaurus.io/">Docusaurus</a>.</p>`,
84
+ },
85
+ prism: {
86
+ additionalLanguages: ['bash', 'ruby'],
87
+ },
88
+ },
89
+ presets: [
90
+ [
91
+ '@docusaurus/preset-classic',
92
+ {
93
+ docs: {
94
+ sidebarPath: require.resolve('./sidebars.js'),
95
+ },
96
+ blog: {
97
+ feedOptions: {
98
+ type: null,
99
+ },
100
+ },
101
+ theme: {
102
+ customCss: require.resolve('./src/css/custom.css'),
103
+ },
104
+ },
105
+ ],
106
+ ],
107
+ };
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "playwright-ruby-docs",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "docusaurus": "docusaurus",
7
+ "start": "docusaurus start",
8
+ "build": "docusaurus build",
9
+ "swizzle": "docusaurus swizzle",
10
+ "deploy": "docusaurus deploy",
11
+ "clear": "docusaurus clear",
12
+ "serve": "docusaurus serve",
13
+ "write-translations": "docusaurus write-translations",
14
+ "write-heading-ids": "docusaurus write-heading-ids"
15
+ },
16
+ "dependencies": {
17
+ "@docusaurus/core": "2.0.0-beta.0",
18
+ "@docusaurus/preset-classic": "2.0.0-beta.0",
19
+ "@mdx-js/react": "^1.6.21",
20
+ "@svgr/webpack": "^5.5.0",
21
+ "clsx": "^1.1.1",
22
+ "file-loader": "^6.2.0",
23
+ "react": "^17.0.1",
24
+ "react-dom": "^17.0.1",
25
+ "url-loader": "^4.1.1"
26
+ },
27
+ "browserslist": {
28
+ "production": [
29
+ ">0.5%",
30
+ "not dead",
31
+ "not op_mini all"
32
+ ],
33
+ "development": [
34
+ "last 1 chrome version",
35
+ "last 1 firefox version",
36
+ "last 1 safari version"
37
+ ]
38
+ }
39
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Creating a sidebar enables you to:
3
+ - create an ordered group of docs
4
+ - render a sidebar for each doc of that group
5
+ - provide next/previous navigation
6
+
7
+ The sidebars can be generated from the filesystem, or explicitly defined here.
8
+
9
+ Create as many sidebars as you want.
10
+ */
11
+
12
+ module.exports = {
13
+ docsSidebar: [{ type: 'autogenerated', dirName: 'article' }],
14
+ apiSidebar: [{ type: 'autogenerated', dirName: 'api' }],
15
+ };
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+ import styles from './HomepageFeatures.module.css';
4
+
5
+ const FeatureList = [
6
+ {
7
+ title: 'Test across modern browsers',
8
+ Svg: require('../../static/img/undraw_windows.svg').default,
9
+ description: (
10
+ <>
11
+ Provide browser automation for Chrome, MS Edge, Firefox, and WebKit.
12
+ </>
13
+ ),
14
+ },
15
+ {
16
+ title: 'Make more relyable tests',
17
+ Svg: require('../../static/img/undraw_dropdown_menu.svg').default,
18
+ description: (
19
+ <>
20
+ Automate with JavaScript-based Playwright protocol, which precisely wait for elements.
21
+ </>
22
+ ),
23
+ },
24
+ {
25
+ title: 'Work with Ruby on Rails',
26
+ Svg: require('../../static/img/undraw_web_development.svg').default,
27
+ description: (
28
+ <>
29
+ Keep your existing system specs for Rails. Just use <a href="https://rubygems.org/gems/capybara-playwright-driver">capybara-playwright-driver</a>.
30
+ </>
31
+ ),
32
+ },
33
+ ];
34
+
35
+ function Feature({ Svg, title, description }) {
36
+ return (
37
+ <div className={clsx('col col--4')}>
38
+ <div className="text--center">
39
+ <Svg className={styles.featureSvg} alt={title} />
40
+ </div>
41
+ <div className="text--center padding-horiz--md">
42
+ <h3>{title}</h3>
43
+ <p>{description}</p>
44
+ </div>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ export default function HomepageFeatures() {
50
+ return (
51
+ <section className={styles.features}>
52
+ <div className="container">
53
+ <div className="row">
54
+ {FeatureList.map((props, idx) => (
55
+ <Feature key={idx} {...props} />
56
+ ))}
57
+ </div>
58
+ </div>
59
+ </section>
60
+ );
61
+ }
@@ -0,0 +1,13 @@
1
+ /* stylelint-disable docusaurus/copyright-header */
2
+
3
+ .features {
4
+ display: flex;
5
+ align-items: center;
6
+ padding: 2rem 0;
7
+ width: 100%;
8
+ }
9
+
10
+ .featureSvg {
11
+ height: 200px;
12
+ width: 200px;
13
+ }
@@ -0,0 +1,44 @@
1
+ /* stylelint-disable docusaurus/copyright-header */
2
+ /**
3
+ * Any CSS included here will be global. The classic template
4
+ * bundles Infima by default. Infima is a CSS framework designed to
5
+ * work well for content-centric websites.
6
+ */
7
+
8
+ /* You can override the default Infima variables here. */
9
+ :root {
10
+ --ifm-color-primary: #0094d9;
11
+ --ifm-color-primary-dark: #0085c3;
12
+ --ifm-color-primary-darker: #0076ad;
13
+ --ifm-color-primary-darkest: #006797;
14
+ --ifm-color-primary-light: #199edc;
15
+ --ifm-color-primary-lighter: #32a9e0;
16
+ --ifm-color-primary-lightest: #4cb4e4;
17
+
18
+ --ifm-code-font-size: 95%;
19
+ }
20
+
21
+ .docusaurus-highlight-code-line {
22
+ background-color: rgb(72, 77, 91);
23
+ display: block;
24
+ margin: 0 calc(-1 * var(--ifm-pre-padding));
25
+ padding: 0 var(--ifm-pre-padding);
26
+ }
27
+
28
+ .header-github-link:hover {
29
+ opacity: 0.6;
30
+ }
31
+
32
+ .header-github-link:before {
33
+ content: '';
34
+ width: 24px;
35
+ height: 24px;
36
+ display: flex;
37
+ background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
38
+ no-repeat;
39
+ }
40
+
41
+ html[data-theme='dark'] .header-github-link:before {
42
+ background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
43
+ no-repeat;
44
+ }
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+ import Layout from '@theme/Layout';
4
+ import Link from '@docusaurus/Link';
5
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6
+ import styles from './index.module.css';
7
+ import HomepageFeatures from '../components/HomepageFeatures';
8
+
9
+ function HomepageHeader() {
10
+ const { siteConfig } = useDocusaurusContext();
11
+ return (
12
+ <header className={clsx('hero hero--dark', styles.heroBanner)}>
13
+ <div className="container">
14
+ <h1 className="hero__title">{siteConfig.title}</h1>
15
+ <p className="hero__subtitle">{siteConfig.tagline}</p>
16
+ <div className={styles.indexCtas}>
17
+ <Link
18
+ className="button button--lg button--primary"
19
+ to="/docs/article/getting_started">
20
+ Getting started
21
+ </Link>
22
+
23
+ <span className={styles.indexCtasGitHubButtonWrapper}>
24
+ <iframe
25
+ className={styles.indexCtasGitHubButton}
26
+ src="https://ghbtns.com/github-btn.html?user=YusukeIwaki&amp;repo=playwright-ruby-client&amp;type=star&amp;count=true&amp;size=large"
27
+ width={160}
28
+ height={30}
29
+ title="GitHub Stars"
30
+ />
31
+ </span>
32
+ </div>
33
+ </div>
34
+ </header>
35
+ );
36
+ }
37
+
38
+ export default function Home() {
39
+ const { siteConfig } = useDocusaurusContext();
40
+ return (
41
+ <Layout
42
+ title={siteConfig.title}
43
+ description="Cross-browser end-to-end testing for modern web apps">
44
+ <HomepageHeader />
45
+ <main>
46
+ <HomepageFeatures />
47
+ </main>
48
+ </Layout>
49
+ );
50
+ }