Skip to content

Commit

Permalink
Add playwright (#138)
Browse files Browse the repository at this point in the history
* playwright init

* add seo tests

* fix lint

* fixup github workflow
cartogram authored Oct 28, 2022
1 parent 68d7043 commit aebd00c
Showing 12 changed files with 317 additions and 517 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -7,5 +7,7 @@ module.exports = {
'no-useless-escape': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'no-case-declarations': 'off',
// TODO: Remove jest plugin from hydrogen/eslint-plugin
'jest/no-deprecated-functions': 'off',
},
};
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -25,3 +25,36 @@ jobs:

- name: 🔬 Lint
run: npm run lint

test:
name: ⚫️ Playwright tests
timeout-minutes: 60
runs-on: shopify-ubuntu-latest
steps:
- name: 🛑 Cancel Previous Runs
uses: styfle/[email protected]

- name: ⬇️ Checkout repo
uses: actions/checkout@v3

- name: ⎔ Setup node
uses: actions/setup-node@v3
with:
node-version: 16

- name: 📥 Install dependencies
run: npm ci

- name: 💽 Install Playwright Browsers
run: npx playwright install --with-deps

- name: 🍄 Run Playwright tests
run: npx playwright test

- name: 🗒 Report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -7,4 +7,7 @@ node_modules
/.mf
.env
/app/styles/app.css
.vscode
.vscode
/test-results/
/playwright-report/
/playwright/.cache/
1 change: 1 addition & 0 deletions app/data/index.ts
Original file line number Diff line number Diff line change
@@ -169,6 +169,7 @@ const LAYOUT_QUERY = `#graphql
) @inContext(language: $language) {
shop {
name
description
}
headerMenu: menu(handle: $headerMenuHandle) {
id
98 changes: 77 additions & 21 deletions app/lib/seo/common.tsx
Original file line number Diff line number Diff line change
@@ -89,7 +89,7 @@ function getSeoDefaults(data: any, match: RouteMatch): SeoDescriptor {
type = 'root';
}

return {
const defaults = {
type,
site: data?.shop?.name,
defaultTitle: data?.shop?.name,
@@ -103,9 +103,11 @@ function getSeoDefaults(data: any, match: RouteMatch): SeoDescriptor {
openGraph: {},
url: pathname,
tags: [],
title: data[type]?.seo?.title,
description: data?.product?.seo?.description,
title: data?.layout?.shop?.title,
description: data?.layout?.shop?.description,
};

return defaults;
}

export function useSeoConfig(): {seo: SeoDescriptor; matches: RouteMatch[]} {
@@ -146,20 +148,37 @@ export function useHeadTags(seo: SeoDescriptor) {
'@type': 'Thing',
};

const {titleTemplate, defaultTitle, ...rest} = seo;
const title = getTitle({title: rest.title, titleTemplate});
const {
titleTemplate,
defaultTitle,
bypassTitleTemplate,
noindex,
nofollow,
...rest
} = seo;

const title = getTitle({
title: rest.title,
defaultTitle,
bypassTitleTemplate,
titleTemplate,
});

Object.entries(rest).forEach(([key, value]) => {
if (Array.isArray(value)) {
switch (key) {
case 'tags':
const keywords = value.join(',');
tags.push(<meta name="keywords" content={keywords} />);

LdJson.keywords = keywords;
if (keywords.length > 0) {
tags.push(
<meta key="keywords" name="keywords" content={keywords} />,
);
LdJson.keywords = keywords;
}

break;
case 'images':
console.log(value);
(value as ImageOptions[]).forEach((image) => {
const {url, width, height, alt} = image;

@@ -207,14 +226,23 @@ export function useHeadTags(seo: SeoDescriptor) {
case 'twitter':
const {handle} = value as TwitterOptions;

links.push(<link rel="me" href={`https://twitter.com/${handle}`} />);
if (handle) {
links.push(
<link
key={`me:${handle}`}
rel="me"
href={`https://twitter.com/${handle}`}
/>,
);
}
break;

case 'robots':
const {noArchive, noSnippet, maxSnippet, unAvailableAfter} =
(value as RobotsOptions) ?? {};

const robotsParams = [
noindex ? 'noindex' : 'index',
nofollow ? 'nofollow' : 'follow',
noArchive && 'noarchive',
noSnippet && 'nosnippet',
maxSnippet && `max-snippet:${maxSnippet}`,
@@ -223,10 +251,12 @@ export function useHeadTags(seo: SeoDescriptor) {

const robotsContent = robotsParams.filter(Boolean).join(',');

tags.push(
<meta key="robots" name="robots" content={robotsContent} />,
<meta key="googlebot" name="googlebot" content={robotsContent} />,
);
if (robotsContent) {
tags.push(
<meta key="robots" name="robots" content={robotsContent} />,
<meta key="googlebot" name="googlebot" content={robotsContent} />,
);
}

break;
}
@@ -236,22 +266,48 @@ export function useHeadTags(seo: SeoDescriptor) {

switch (key) {
case 'title':
tags.push(<title>{title}</title>);
ogTags.push(<meta name="og:title" property={value} />);
twitterTags.push(<meta name="twitter:title" property={value} />);
tags.push(<title key={title}>{title}</title>);
ogTags.push(
<meta
key={`og:title:${value}`}
property="og:title"
content={value}
/>,
);
twitterTags.push(
<meta
key={`twitter:title:${value}`}
name="twitter:title"
content={value}
/>,
);

LdJson.name = value;

break;
case 'description':
tags.push(<meta name="description" content={value} />);
ogTags.push(<meta name="og:description" property={value} />);
twitterTags.push(<meta name="twitter:description" property={value} />);
tags.push(
<meta key="description" name="description" content={value} />,
);
ogTags.push(
<meta
key="og:description"
property="og:description"
content={value}
/>,
);
twitterTags.push(
<meta
key="twitter:description"
name="twitter:description"
content={value}
/>,
);

break;

case 'url':
links.push(<link rel="canonical" href={value} />);
links.push(<link key="canonical" rel="canonical" href={value} />);

break;
default:
12 changes: 9 additions & 3 deletions app/lib/seo/debugger.tsx
Original file line number Diff line number Diff line change
@@ -45,12 +45,18 @@ export function Debugger() {
if (typeof value !== 'string') {
return null;
}
return <Item property={property} value={value} />;
return (
<Item
key={`${property}${value}`}
property={property}
value={value}
/>
);
})}

<div className="py-4 px-4">
{matches.map(({id, handle, data}: RouteMatch, index: number) => (
<div key={index}>
<div key={id}>
<span className="block font-bold block text-xs pb-2 ">{id}</span>
<div className="whitespace-pre font-mono px-4 py-2 mb-4 text-gray-600 rounded-sm text-[10px] bg-gray-100 text-[10px] px-4">
{JSON.stringify(
@@ -85,7 +91,7 @@ export function Debugger() {
entries.map((entry: React.ReactElement, index: number) => (
<span
className="whitespace-nowrap font-mono rounded-sm text-gray-600 rounded-sm text-[10px] bg-gray-100 text-[10px] px-4"
key={index}
key={entry.props.name || index}
>
{renderToString(entry)}
</span>
4 changes: 0 additions & 4 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -30,10 +30,6 @@ export const handle = {
title: data.layout.shop.name,
bypassTitleTemplate: true,
titleTemplate: `%s | ${data.layout.shop.name}`,
robots: {
index: true,
follow: true,
},
}),
};

7 changes: 0 additions & 7 deletions app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -100,13 +100,6 @@ export async function loader({params, context: {storefront}}: LoaderArgs) {
});
}

export const meta: MetaFunction = ({data}) => {
return {
title: data?.shop?.name,
description: data?.shop?.description,
};
};

export default function Homepage() {
const {
primaryHero,
Loading

0 comments on commit aebd00c

Please sign in to comment.