Skip to content

Commit

Permalink
✅ Added Playwright test suite for CN, JP, KR chars
Browse files Browse the repository at this point in the history
Also added accessible attr for input/textarea components to select them in Playwright. Updated snapshots accordingly.
  • Loading branch information
KemingHe committed Dec 21, 2024
1 parent 738dcf4 commit b585e38
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 41 deletions.
7 changes: 7 additions & 0 deletions .changeset/orange-items-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"socialify": patch
---

Added Playwright test for full variant of CN, JP, and KR (Korean).

Added accessbility attributes for generic input/textbox components. Updated snapshots.
87 changes: 87 additions & 0 deletions .playwright/cjkCharsCorrectness.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import Buffer from '@playwright/test'
import { type Page, expect, test } from '@playwright/test'

// IMPORTANT: Playwright is not setup with import aliases, use relative paths.
import getClipboardText from './utils/getClipboardText'

const customTimeout = { timeout: 30000 }
const componentUpdateTimeout = 1000
const repo: string = 'wei/socialify'
const expectedPageURLEnding: string =
'?custom_description=%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%2C%20%E7%B9%81%E9%AB%94%E4%B8%AD%E6%96%87%20(f%C3%A1n%20t%C7%90%20zh%C5%8Dng%20w%C3%A9n)%2C%20%E3%81%AB%E3%81%BB%E3%82%93%E3%81%94%20%E3%81%B2%E3%82%89%E3%81%8C%E3%81%AA%2C%20%E3%83%8B%E3%83%9B%E3%83%B3%E3%82%B4%20%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A%2C%20%E6%97%A5%E6%9C%AC%E8%AA%9E%20%E6%BC%A2%E5%AD%97%2C%20%ED%95%9C%EA%B5%AD%EC%96%B4%20%E9%9F%93%E5%9C%8B%E8%AA%9E%2C%20%EC%A1%B0%EC%84%A0%EB%A7%90%20%E6%9C%9D%E9%AE%AE%EB%A7%90&description=1&font=Jost&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fwei%2Fsocialify%2F1f520b6bfe799300bcff9edcdc330828d681d382%2Fapp%2Ficon.svg&name=1&owner=1&pattern=Diagonal%20Stripes&theme=Dark'
const expectedClipboardURLEnding: string =
'/wei/socialify/image?custom_description=%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87%2C+%E7%B9%81%E9%AB%94%E4%B8%AD%E6%96%87+%28f%C3%A1n+t%C7%90+zh%C5%8Dng+w%C3%A9n%29%2C+%E3%81%AB%E3%81%BB%E3%82%93%E3%81%94+%E3%81%B2%E3%82%89%E3%81%8C%E3%81%AA%2C+%E3%83%8B%E3%83%9B%E3%83%B3%E3%82%B4+%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A%2C+%E6%97%A5%E6%9C%AC%E8%AA%9E+%E6%BC%A2%E5%AD%97%2C+%ED%95%9C%EA%B5%AD%EC%96%B4+%E9%9F%93%E5%9C%8B%E8%AA%9E%2C+%EC%A1%B0%EC%84%A0%EB%A7%90+%E6%9C%9D%E9%AE%AE%EB%A7%90&description=1&font=Jost&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fwei%2Fsocialify%2F1f520b6bfe799300bcff9edcdc330828d681d382%2Fapp%2Ficon.svg&name=1&owner=1&pattern=Diagonal+Stripes&theme=Dark'
// IMPORTANT: Due to auto encoding and generation of the URLs,
// the clipboard content is DIFFERENT from the page URL;
// thus, the expected content is separately defined and checked for each.

test.beforeEach(async ({ page }: { page: Page }): Promise<void> => {
await page.goto('/', customTimeout)
await page.waitForLoadState('networkidle', customTimeout)
})

test.describe('All variants of Chinese, Japanese, and Korean characters in "Description" field:', () => {
test(`renders correctly and consistently`, async ({
page,
}: { page: Page }): Promise<void> => {
// Input and submit the repo following accessibility best practices.
await page.fill('input[name="repo-input"]', repo)
await page.waitForTimeout(componentUpdateTimeout)
await page.click('button[type="submit"]')

// Wait for complete navigation to the preview config page.
await page.waitForSelector('button:has-text("URL")', customTimeout)
await page.waitForLoadState('networkidle', customTimeout)

// De-select the "Stars" option (for consistent preview image),
// and select the "Description" option for custom chat input.
await page.click('input[name="stargazers"]')
await page.waitForTimeout(componentUpdateTimeout)
await page.click('input[name="description"]')
await page.waitForTimeout(componentUpdateTimeout)

// Use complete costom styling for the preview image, including:
// - 'Dark' theme
// - 'Jost' font
// - 'Diagonal Stripes' pattern
// - The Socialify logo for the 'SVG Logo' option
// - Custom 'Description' field with all variants of Chinese, Japanese, and Korean characters.
await page.selectOption('select[name="theme"]', { label: 'Dark' })
await page.waitForTimeout(componentUpdateTimeout)
await page.selectOption('select[name="font"]', { label: 'Jost' })
await page.waitForTimeout(componentUpdateTimeout)
await page.selectOption('select[name="pattern"]', {
label: 'Diagonal Stripes',
})
await page.waitForTimeout(componentUpdateTimeout)
await page.fill(
'input[name="logo"]',
'https://raw.githubusercontent.com/wei/socialify/1f520b6bfe799300bcff9edcdc330828d681d382/app/icon.svg'
)
await page.waitForTimeout(componentUpdateTimeout)
await page.fill(
'textarea[name="description"]',
'简体中文, 繁體中文 (fán tǐ zhōng wén), にほんご ひらがな, ニホンゴ カタカナ, 日本語 漢字, 한국어 韓國語, 조선말 朝鮮말'
)
await page.waitForTimeout(componentUpdateTimeout)
await page.waitForLoadState('networkidle', customTimeout)

// Check page url before checking the clipboard content.
const pageURL: string = page.url()
expect(pageURL.endsWith(expectedPageURLEnding)).toBe(true)

// Compare the clipboard content to the expected preview URL.
// (Only check the end of the URL due to dynamic localhost port allocation.)
await page.click('button:has-text("URL")')
await page.waitForTimeout(componentUpdateTimeout)
const clipboardURL: string = await getClipboardText(page)
expect(clipboardURL.endsWith(expectedClipboardURLEnding)).toBe(true)

// Visit the image URL and snapshot the image.
await page.goto(clipboardURL, customTimeout)
await page.waitForLoadState('networkidle', customTimeout)

const previewImage: typeof Buffer = await page.screenshot()
expect(previewImage).toMatchSnapshot()
})
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 7 additions & 8 deletions .playwright/imageAPIEndpoints.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { type Page, expect, test } from '@playwright/test'

const customTimeout = { timeout: 30000 }
const defaultImageURL: string =
'/wei/socialify/image?description=1&font=Raleway&language=1&name=1&owner=1&pattern=Diagonal%20Stripes&theme=Dark'
const svgImageURL: string =
'/wei/socialify/svg?description=1&font=Raleway&language=1&name=1&owner=1&pattern=Diagonal%20Stripes&theme=Dark'
const pngImageURL: string =
'/wei/socialify/png?description=1&font=Raleway&language=1&name=1&owner=1&pattern=Diagonal%20Stripes&theme=Dark'
const repo: string = 'wei/socialify'
const searchParamsString: string =
'?description=1&font=Raleway&language=1&name=1&owner=1&pattern=Diagonal%20Stripes&theme=Dark'
const defaultImageURL: string = repo + '/image' + searchParamsString
const svgImageURL: string = repo + '/svg' + searchParamsString
const pngImageURL: string = repo + '/png' + searchParamsString
// Backward compatibility route, see ./next.config.js rewrite rules.
const jpgImageURL: string =
'/wei/socialify/jpg?description=1&font=Raleway&language=1&name=1&owner=1&pattern=Diagonal%20Stripes&theme=Dark'
const jpgImageURL: string = repo + '/jpg' + searchParamsString

test.describe('Socialify image api', () => {
test('respond consistently for default endpoint', async ({
Expand Down
24 changes: 11 additions & 13 deletions .playwright/mainUIConsistency.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,22 @@ test.describe('Socialify UI:', () => {
await page.goto(repoPreviewURL, customPageLoadTimeout)
await page.waitForLoadState('networkidle', customPageLoadTimeout)

await page
.locator('[data-input-key="logo"] input')
.fill(
'data:image/svg+xml,%3Csvg%20width%3D%22800px%22%20height%3D%22800px%22%20viewBox%3D%220%200%201024%201024%22%20class%3D%22icon%22%20%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M373.2%20600.3h278.7v8H373.2z%22%20fill%3D%22%23999999%22%20%2F%3E%3Cpath%20d%3D%22M512.6%20948.3h-9.8c-58.7%200-106.7-48-106.7-106.7v-212h259v176.2c0%2078.4-64.2%20142.5-142.5%20142.5z%22%20fill%3D%22%23F9C0C0%22%20%2F%3E%3Cpath%20d%3D%22M511.7%20958.8c-40.7%200-79-15.9-108-44.9s-44.9-67.3-44.9-108V209.2h-32.2c-11.4%200-20.7-9.3-20.7-20.7v-17.6c0-11.4%209.3-20.7%2020.7-20.7h370.1c11.4%200%2020.7%209.3%2020.7%2020.7v17.6c0%2011.4-9.3%2020.7-20.7%2020.7h-32.2v596.7c0%2040.7-15.9%2079-44.9%20108-28.9%2028.9-67.2%2044.9-107.9%2044.9zM326.6%20165.1c-3.2%200-5.7%202.6-5.7%205.7v17.6c0%203.2%202.6%205.7%205.7%205.7h47.2v611.7c0%2036.7%2014.4%2071.3%2040.5%2097.4%2026.1%2026.1%2060.7%2040.5%2097.4%2040.5s71.3-14.4%2097.4-40.5c26.1-26.1%2040.5-60.7%2040.5-97.4V194.2h47.2c3.2%200%205.7-2.6%205.7-5.7v-17.6c0-3.2-2.6-5.7-5.7-5.7l-370.2-0.1z%22%20fill%3D%22%23999999%22%20%2F%3E%3Cpath%20d%3D%22M373.2%20193.8h50.7v8h-50.7zM466.8%20193.8h185.1v8H466.8z%22%20fill%3D%22%23999999%22%20%2F%3E%3Cpath%20d%3D%22M535.7%20558.5c-14.1%200-25.5-11.4-25.5-25.5s11.4-25.5%2025.5-25.5%2025.5%2011.4%2025.5%2025.5c0%2014-11.4%2025.5-25.5%2025.5z%20m0-43c-9.6%200-17.5%207.8-17.5%2017.5%200%209.6%207.8%2017.5%2017.5%2017.5s17.5-7.8%2017.5-17.5-7.9-17.5-17.5-17.5zM458.1%20417.6c-21.3%200-38.6-17.3-38.6-38.6s17.3-38.6%2038.6-38.6%2038.6%2017.3%2038.6%2038.6-17.3%2038.6-38.6%2038.6z%20m0-69.2c-16.9%200-30.6%2013.7-30.6%2030.6s13.7%2030.6%2030.6%2030.6%2030.6-13.7%2030.6-30.6-13.7-30.6-30.6-30.6zM566.7%20107.3c-11.4%200-20.7-9.3-20.7-20.7s9.3-20.7%2020.7-20.7%2020.7%209.3%2020.7%2020.7-9.2%2020.7-20.7%2020.7z%20m0-33.4c-7%200-12.7%205.7-12.7%2012.7s5.7%2012.7%2012.7%2012.7%2012.7-5.7%2012.7-12.7-5.7-12.7-12.7-12.7zM540.5%20299.5c-16.7%200-30.3-13.6-30.3-30.3s13.6-30.3%2030.3-30.3%2030.3%2013.6%2030.3%2030.3-13.6%2030.3-30.3%2030.3z%20m0-52.6c-12.3%200-22.3%2010-22.3%2022.3s10%2022.3%2022.3%2022.3%2022.3-10%2022.3-22.3-10-22.3-22.3-22.3z%22%20fill%3D%22%23CE0202%22%20%2F%3E%3C%2Fsvg%3E'
)

const errorMessage = await page
.locator('[data-input-key="logo"] .label-text-alt.text-red-400')
.textContent()
expect(errorMessage).toBe(
'URI is too long, please use an SVG image URL instead.'
)

await page.click('input[name="stargazers"]')
await page.waitForTimeout(componentUpdateTimeout)
await page.click('input[name="description"]')
await page.waitForTimeout(componentUpdateTimeout)

await page.fill(
'input[name="logo"]',
'data:image/svg+xml,%3Csvg%20width%3D%22800px%22%20height%3D%22800px%22%20viewBox%3D%220%200%201024%201024%22%20class%3D%22icon%22%20%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M373.2%20600.3h278.7v8H373.2z%22%20fill%3D%22%23999999%22%20%2F%3E%3Cpath%20d%3D%22M512.6%20948.3h-9.8c-58.7%200-106.7-48-106.7-106.7v-212h259v176.2c0%2078.4-64.2%20142.5-142.5%20142.5z%22%20fill%3D%22%23F9C0C0%22%20%2F%3E%3Cpath%20d%3D%22M511.7%20958.8c-40.7%200-79-15.9-108-44.9s-44.9-67.3-44.9-108V209.2h-32.2c-11.4%200-20.7-9.3-20.7-20.7v-17.6c0-11.4%209.3-20.7%2020.7-20.7h370.1c11.4%200%2020.7%209.3%2020.7%2020.7v17.6c0%2011.4-9.3%2020.7-20.7%2020.7h-32.2v596.7c0%2040.7-15.9%2079-44.9%20108-28.9%2028.9-67.2%2044.9-107.9%2044.9zM326.6%20165.1c-3.2%200-5.7%202.6-5.7%205.7v17.6c0%203.2%202.6%205.7%205.7%205.7h47.2v611.7c0%2036.7%2014.4%2071.3%2040.5%2097.4%2026.1%2026.1%2060.7%2040.5%2097.4%2040.5s71.3-14.4%2097.4-40.5c26.1-26.1%2040.5-60.7%2040.5-97.4V194.2h47.2c3.2%200%205.7-2.6%205.7-5.7v-17.6c0-3.2-2.6-5.7-5.7-5.7l-370.2-0.1z%22%20fill%3D%22%23999999%22%20%2F%3E%3Cpath%20d%3D%22M373.2%20193.8h50.7v8h-50.7zM466.8%20193.8h185.1v8H466.8z%22%20fill%3D%22%23999999%22%20%2F%3E%3Cpath%20d%3D%22M535.7%20558.5c-14.1%200-25.5-11.4-25.5-25.5s11.4-25.5%2025.5-25.5%2025.5%2011.4%2025.5%2025.5c0%2014-11.4%2025.5-25.5%2025.5z%20m0-43c-9.6%200-17.5%207.8-17.5%2017.5%200%209.6%207.8%2017.5%2017.5%2017.5s17.5-7.8%2017.5-17.5-7.9-17.5-17.5-17.5zM458.1%20417.6c-21.3%200-38.6-17.3-38.6-38.6s17.3-38.6%2038.6-38.6%2038.6%2017.3%2038.6%2038.6-17.3%2038.6-38.6%2038.6z%20m0-69.2c-16.9%200-30.6%2013.7-30.6%2030.6s13.7%2030.6%2030.6%2030.6%2030.6-13.7%2030.6-30.6-13.7-30.6-30.6-30.6zM566.7%20107.3c-11.4%200-20.7-9.3-20.7-20.7s9.3-20.7%2020.7-20.7%2020.7%209.3%2020.7%2020.7-9.2%2020.7-20.7%2020.7z%20m0-33.4c-7%200-12.7%205.7-12.7%2012.7s5.7%2012.7%2012.7%2012.7%2012.7-5.7%2012.7-12.7-5.7-12.7-12.7-12.7zM540.5%20299.5c-16.7%200-30.3-13.6-30.3-30.3s13.6-30.3%2030.3-30.3%2030.3%2013.6%2030.3%2030.3-13.6%2030.3-30.3%2030.3z%20m0-52.6c-12.3%200-22.3%2010-22.3%2022.3s10%2022.3%2022.3%2022.3%2022.3-10%2022.3-22.3-10-22.3-22.3-22.3z%22%20fill%3D%22%23CE0202%22%20%2F%3E%3C%2Fsvg%3E'
)
await page.waitForTimeout(componentUpdateTimeout)

const errorMessage = await page.locator('#logo-error').textContent()
expect(errorMessage).toBe(
'URI is too long, please use an SVG image URL instead.'
)

const image = await page.screenshot(customScreenshotOptions)
expect(image).toMatchSnapshot(customDiffPixelRatio)
})
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 3 additions & 6 deletions .playwright/simpleUserStory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { type Page, expect, test } from '@playwright/test'

// IMPORTANT: Playwright is not setup with import aliases, use relative paths.
import getClipboardText from './utils/getClipboardText'

const customTimeout = { timeout: 30000 }
const componentUpdateTimeout = 1000
const repo: string = 'wei/socialify'
Expand All @@ -8,12 +11,6 @@ const expectedConfigURL: string =
const expectedImageURLRegExp: RegExp =
/\/wei\/socialify\/image\?description=1&font=Source\+Code\+Pro&language=1&name=1&owner=1&theme=Light$/

async function getClipboardText(page: Page): Promise<string> {
return await page.evaluate(async () => {
return await navigator.clipboard.readText()
})
}

test.beforeEach(async ({ page }: { page: Page }): Promise<void> => {
await page.goto('/', customTimeout)
await page.waitForLoadState('networkidle', customTimeout)
Expand Down
7 changes: 7 additions & 0 deletions .playwright/utils/getClipboardText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type Page } from '@playwright/test'

export default async function getClipboardText(page: Page): Promise<string> {
return await page.evaluate(async () => {
return await navigator.clipboard.readText()
})
}
15 changes: 12 additions & 3 deletions src/components/configuration/checkBoxWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,27 @@ const CheckBoxWrapper = ({
}: CheckBoxProps) => {
return (
<div className="form-control">
<label className="label cursor-pointer justify-start gap-2">
<label
className="label cursor-pointer justify-start gap-2"
htmlFor={keyName}
>
<input
id={keyName}
name={keyName}
className="checkbox checkbox-sm bg-base-100"
type="checkbox"
name={keyName}
checked={!!checked}
disabled={disabled}
onChange={(e) => {
handleChange({ state: e.target.checked }, keyName)
}}
aria-disabled={disabled}
aria-checked={!!checked}
aria-labelledby={`${keyName}-title`}
/>
<span className="label-text font-semibold">{title}</span>
<span className="label-text font-semibold" id={`${keyName}-title`}>
{title}
</span>
</label>
</div>
)
Expand Down
20 changes: 16 additions & 4 deletions src/components/configuration/inputWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ const InputWrapper = ({
}: InputProps) => {
return (
<div className="form-control w-full" data-input-key={keyName}>
<label className="label">
<span className="label-text font-semibold">{title}</span>
{alt && <span className="label-text-alt font-semibold">{alt}</span>}
<label className="label" htmlFor={keyName}>
<span className="label-text font-semibold" id={`${keyName}-title`}>
{title}
</span>
{alt && (
<span className="label-text-alt font-semibold" id={`${keyName}-alt`}>
{alt}
</span>
)}
</label>
<input
className={clsx('input input-sm input-bordered font-semibold w-full', {
'input-error': error,
})}
id={keyName}
name={keyName}
type="text"
value={value || ''}
disabled={!!disabled}
Expand All @@ -42,10 +50,14 @@ const InputWrapper = ({
handleChange({ val: e.target.value, required: true }, keyName)
}}
maxLength={maxlen}
aria-labelledby={`${keyName}-title ${alt ? `${keyName}-alt` : ''}`}
aria-invalid={!!error}
/>
{error && (
<div className="label">
<span className="label-text-alt text-red-400">{error}</span>
<span className="label-text-alt text-red-400" id={`${keyName}-error`}>
{error}
</span>
</div>
)}
</div>
Expand Down
14 changes: 10 additions & 4 deletions src/components/configuration/selectWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@ const SelectWrapper = ({
return (
<div className="form-control w-full">
<label className="label" htmlFor={selectId}>
<span className="label-text font-semibold">{title}</span>
{alt && <span className="label-text-alt font-semibold">{alt}</span>}
<span className="label-text font-semibold" id={`${selectId}-title`}>
{title}
</span>
{alt && (
<span className="label-text-alt font-semibold" id={`${selectId}-alt`}>
{alt}
</span>
)}
</label>
<select
id={selectId}
name={keyName}
className="select select-bordered select-sm font-semibold"
onChange={(e) => {
handleChange({ val: e.target.value, required: true }, keyName)
}}
name={keyName}
value={value}
aria-labelledby={selectId}
aria-labelledby={`${selectId}-title ${alt ? `${selectId}-alt` : ''}`}
>
{map.map(({ key, label }) => {
return (
Expand Down
19 changes: 16 additions & 3 deletions src/components/configuration/textAreaWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,30 @@ const TextAreaWrapper = ({
return (
<div className="form-control">
{title && (
<label className="label">
<span className="label-text font-semibold">{title}</span>
{alt && <span className="label-text-alt font-semibold">{alt}</span>}
<label className="label" htmlFor={keyName}>
<span className="label-text font-semibold" id={`${keyName}-title`}>
{title}
</span>
{alt && (
<span
className="label-text-alt font-semibold"
id={`${keyName}-alt`}
>
{alt}
</span>
)}
</label>
)}
<textarea
id={keyName}
name={keyName}
className="textarea textarea-bordered h-20 font-semibold"
value={internalValue}
onChange={processChange}
disabled={disabled}
placeholder={placeholder}
aria-disabled={disabled}
aria-labelledby={`${keyName}-title ${alt ? `${keyName}-alt` : ''}`}
/>
</div>
)
Expand Down

0 comments on commit b585e38

Please sign in to comment.