@sellsuki-org/sellsuki-components-test
v1.0.3
Published
Playwright test helpers for @sellsuki-org/sellsuki-components
Downloads
0
Keywords
Readme
@sellsuki-org/sellsuki-components-test
test function กลางของ ssk- component* — คู่กับ @sellsuki-org/sellsuki-components
sellsuki-components เก็บ component ให้ dev ใช้ → repo นี้เก็บ test function ของ component เดียวกัน ให้ QA ใช้
ทุกทีม install จากที่นี่ เพิ่ม / ลด / แก้ ทำที่นี่ที่เดียว (ดู CONTRIBUTING.md)
ติดตั้ง
pin แบบเป๊ะเสมอ (--save-exact) เพื่อให้ทุกทีมได้ version เดียวกัน
npm i -D --save-exact @sellsuki-org/sellsuki-components-test"@sellsuki-org/sellsuki-components-test": "1.0.0" // ไม่มี ^ทำไมต้อง exact: ^1.0.0 แปลว่า "1.0.0 ขึ้นไปแต่ต่ำกว่า 2.0.0" → ทีมที่ install
ต่างเวลากันจะได้ตัวคนละเวอร์ชัน ทั้งที่ package.json เขียนเหมือนกันเป๊ะ
→ เปิดเทียบกันแล้วเข้าใจผิดว่าตรงกัน
อัปเดตเมื่อพร้อม (แต่ละทีมทำเอง ไม่มี auto-update):
npm i -D --save-exact @sellsuki-org/[email protected]
npm ls @sellsuki-org/sellsuki-components-test # เช็คว่าจริง ๆ ใช้ตัวไหนอยู่แล้ว commit package.json + package-lock.json — CI ที่ใช้ npm ci จะยึด lock
เพราะฉะนั้น version ใหม่จะเข้า suite ก็ต่อเมื่อมีคน commit lock ใหม่
@playwright/test เป็น peerDependency (>=1.56) — ใช้ตัวที่ repo คุณมีอยู่แล้ว
Versioning
version ของ package นี้เป็นของตัวเอง ไม่ล้อกับ sellsuki-components
เพราะสองอย่างนี้ขับเคลื่อนด้วยคนละเรื่อง — API ของ helper โตตามที่ QA เพิ่ม function ส่วน component library ปล่อยของตามงานของทีม dev ถ้ามัดเลขไว้ด้วยกันจะเกิด release ที่ไม่มีความหมาย (dev ปล่อย minor แต่ helper ไม่ได้แก้อะไร) และเพิ่ม function ใหม่แล้ว bump minor ไม่ได้เพราะ minor ถูกจองไว้
semver ตามความหมายจริง:
| เปลี่ยนอะไร | bump | |---|---| | เพิ่ม helper ตัวใหม่ / เพิ่มเมธอดใน helper เดิม | minor | | แก้ selector ให้ตรง DOM ที่เปลี่ยน / แก้บั๊ก | patch | | เปลี่ยนชื่อ / ลบเมธอด / เปลี่ยน signature | major |
ความเข้ากันได้กับ sellsuki-components
ประกาศไว้ใน peerDependencies (optional) ให้ npm เตือนเอง ไม่ต้องพึ่งตารางที่ไม่มีใครอ่าน:
"peerDependencies": {
"@sellsuki-org/sellsuki-components": ">=0.24.0"
}ไม่ล็อกเพดานเพื่อไม่ให้บล็อกทีมที่อัป component ก่อน แต่ verified แค่ช่วงนี้:
| helper | ทดสอบกับ components | หมายเหตุ |
|---|---|---|
| 1.x | 0.24.x | เขียนจาก source ณ 0.24.6 · e2e ทั้งสอง repo อยู่ ^0.24.7/^0.24.8 → สูงสุด 0.24.8 |
| — | 0.25 – 0.27 | ยังไม่ตรวจ (npm ล่าสุดคือ 0.27.0) ทีมไหนอัป component ก่อนช่วยรัน suite แล้วแจ้งด้วย |
เวลามีใครอัป component ข้าม minor ให้ไล่ดูว่า DOM ที่ helper ยึดเปลี่ยนไหม แล้วอัปเดตตารางนี้ + ปล่อย patch ถ้าต้องแก้ selector
ใช้งาน
import { ToastComponent, GenericTableAssert, SskInputComponent } from '@sellsuki-org/sellsuki-components-test';
const toast = new ToastComponent(page);
await toast.assertSuccess();
await toast.assertMessage('บันทึกสำเร็จ');
const table = new GenericTableAssert(page.getByTestId('order.table'), page.locator('ssk-table-cell'), 6);
await table.assertRowCount(10);
await SskInputComponent.fill(page.getByTestId('amount').locator('input'), '1500');Inventory
รวมมาจาก resources/ssk-component/ ของ sellsuki-e2e-test-playwright (CCS3) และ
patona-e2e-test-playwright — 17 + 19 ไฟล์ → 23 ไฟล์ 24 คลาส
(รวม export ทั้งหมด 27 ตัว = 24 คลาส + DEFAULT_TIMEOUT + ms() + exactTextWithOuterWhitespace())
src/components/ — wrapper ของ ssk-* element
| ไฟล์ | export | element | มาจาก |
|---|---|---|---|
| ssk-badge.component.ts | SskBadgeComponent | ssk-badge | S + P |
| ssk-button.component.ts | SskButtonComponent | ssk-button | P |
| ssk-calendar.component.ts | SskCalendarComponent | ssk-calendar | P |
| ssk-checkbox.component.ts | SskCheckboxComponent | ssk-checkbox | S |
| ssk-confirmation-modal.component.ts | ConfirmationModalComponent | ssk-modal | S + P |
| ssk-date-picker.component.ts | SskDatePickerComponent, SskRangeDatePickerComponent | ssk-date-picker, ssk-range-date-picker | S + P |
| ssk-dropdown.component.ts | SskDropdownComponent | ssk-dropdown | S + P |
| ssk-input.component.ts | SskInputComponent | ssk-input | S + P |
| ssk-modal.component.ts | SskModalComponent | ssk-modal (testid-based) | P |
| ssk-pagination.component.ts | PaginationComponent | ssk-pagination | S + P |
| ssk-radio.component.ts | SskRadioComponent | ssk-radio | S |
| ssk-radio-group.component.ts | SskRadioGroupComponent | ssk-radio-group | P |
| ssk-sidebar.component.ts | SskSidebarComponent | ssk-sidebar | S + P |
| ssk-tab.component.ts | SskTabComponent | ssk-tabs, ssk-tab-button | S + P |
| ssk-table.component.ts | GenericTableAssert | ssk-table, ssk-dynamic-table | S + P |
| ssk-text.component.ts | SskTextComponent | ssk-text | P |
| ssk-textarea.component.ts | SskTextareaComponent | ssk-textarea | P |
| ssk-time-picker.component.ts | SskTimePickerComponent | ssk-time | S + P |
| ssk-toast.component.ts | ToastComponent | ssk-toast | S + P |
| ssk-toggle.component.ts | SskToggleComponent | ssk-toggle | S |
| ssk-tooltip.component.ts | SskTooltipComponent | ssk-tooltip | S |
| ssk-top-navbar.component.ts | SskTopNavbarComponent | ssk-top-navbar | P |
| ssk-upload-image.component.ts | SskUploadImageComponent | widget อัปโหลดรูป | S |
helper กลาง: DEFAULT_TIMEOUT, ms(), exactTextWithOuterWhitespace()
ขอบเขต: อะไรไม่เข้า package นี้
บล็อก UI ที่แอปประกอบขึ้นเอง ไม่เข้า — เก็บไว้ที่ e2e repo ของแอปนั้น
เพราะมัน version ตามแอป ไม่ใช่ตาม sellsuki-components (ดูเหตุผลเต็มใน CONTRIBUTING.md)
ตัวอย่างที่พิจารณาแล้วให้อยู่นอก package: หน้า "ไม่มีสิทธิ์เข้าถึง"
dev ทำ src/components/PermissionDenied.svelte แยกกันในแต่ละแอป (CCS3 ใช้ 7 หน้า / OMS ใช้ 10 หน้า)
และ ssk-wave-icon ที่มันใช้ปรากฏ แค่ที่นี่ที่เดียวต่อแอป → ไม่มี element กลางให้ wrap
เมธอดที่เปลี่ยนชื่อ
ตอนรวมสอง repo มีเมธอด ชื่อซ้ำแต่ความหมายคนละอย่าง จึงตั้งชื่อใหม่ให้บอกชัดว่าเช็คอะไร ชื่อเดิมที่ไม่กำกวมยังอยู่ครบ
| component | ของเดิม | repo | ของใหม่ |
|---|---|---|---|
| ToastComponent | assertVisible(message) | S | assertMessage(message) |
| ToastComponent | assertVisible(testId) | P | assertVisibleByTestId(testId) |
| ToastComponent | assertDismissed(message) | S | assertMessageDismissed(message) |
| SskBadgeComponent | assertText(container, text) | S | assertTextIn(container, text) |
| SskInputComponent | assertError(errorNode, msg) | S | assertErrorNodeText(errorNode, msg) |
| SskInputComponent | assertErrorCleared(errorNode) | S | assertErrorNodeHidden(errorNode) |
| SskDropdownComponent | assertSelectedText(text) (เช็คใน ssk-dropdown-button) | P | assertSelectedTextInButton(text) |
| GenericTableAssert | assertRowCountIs(n) | P | assertRowCount(n) — ชื่อเดิมยังใช้ได้ (deprecated) |
| GenericTableAssert | assertCellTextAt(row, col0, text) | P | assertCellText(row, col1, text) — col เปลี่ยนเป็น 1-based ชื่อเดิมยังใช้ได้ (deprecated) |
| GenericTableAssert | clickCellAt(row, col0) | P | clickCell(row, col1) — ชื่อเดิมยังใช้ได้ (deprecated) |
| ConfirmationModalComponent | default text จาก i18n | S | ต้องส่ง title / content เข้ามาเอง — package กลางไม่ import i18n |
timeoutเดิมของ patona บางเมธอดเป็น 15s ตอนนี้ default เป็น 30s ทุกเมธอด และ override ได้ด้วย{ timeout: 15_000 }เป็น argument สุดท้าย
Migration จากโฟลเดอร์ resources/ssk-component/ เดิม
import จาก package ตรง ๆ แล้วลบไฟล์เดิม — ไม่ต้องทำ shim เหตุผล: โฟลเดอร์ที่เหลือค้างไว้จะดูเหมือนแก้ได้ แล้วสุดท้ายจะมีคนแก้ที่นั่นแทนที่จะมาแก้ที่นี่ ซึ่งพาเรากลับไปสู่ปัญหาเดิม — ถ้าไฟล์ไม่มีอยู่ กฎมันบังคับตัวเอง
# 1
npm i -D @sellsuki-org/sellsuki-components-test
# 2 — rewrite import ทุกจุดให้ชี้ package (path ลึกกี่ชั้นก็ครอบ)
grep -rl "ssk-component/ssk-" resources tests --include='*.ts' --include='*.js' \
| xargs sed -i '' -E "s|from '(\.\./)*ssk-component/ssk-[a-z-]+\.component'|from '@sellsuki-org/sellsuki-components-test'|g"
# 3 — ลบไฟล์ที่ย้ายเข้ามาอยู่ใน package แล้ว
# ⚠️ เหลือไว้เฉพาะไฟล์ที่เป็นของแอปเอง (ดูตารางล่าง)
# 4 — ให้ compiler ไล่จุดที่ใช้เมธอดชื่อเก่า
npx tsc --noEmit
# 5 — แก้ตามที่ฟ้อง (ตาราง "เมธอดที่เปลี่ยนชื่อ" ข้างบน) แล้วรัน suite
ENV=staging npx playwright testไฟล์ที่ต้องเหลือไว้ที่ repo — และควรย้ายออกจาก ssk-component/
2 ไฟล์นี้ ไม่ใช่ wrapper ของ ssk-* element จึงไม่ควรอยู่ในโฟลเดอร์ชื่อ ssk-component/
ย้ายไป resources/pages/common/ แล้วตัด prefix ssk- ออก (โฟลเดอร์นั้นมี precedent อยู่แล้ว
เช่น side-nav.ts = บล็อก UI ที่หลายหน้าใช้ร่วมกันแต่เป็นของแอปเอง — ชนิดเดียวกันเลย)
| ที่อยู่ใหม่ | ทำไมไม่เข้า package |
|---|---|
| resources/pages/common/permission-denied.ts | บล็อก UI ที่แอปประกอบเอง version ตามแอป ไม่ใช่ตาม library |
| resources/pages/common/confirmation-modal.ts | เป็น wrapper ที่ใส่ default i18n ให้ตัวใน package — i18n ต้องอยู่ที่ repo |
| UNSAVED_ALERT_TESTID (patona) | เป็น testid ของแอป ('unsaved-alert' จาก Layout.svelte ของ Seller Center) ไม่ใช่สัญญาของ component — ย้ายไปไว้ resources/const/ แล้ว import จากที่นั่น (ใช้ 5 page object) |
จบแล้ว resources/ssk-component/ ควร ไม่มีอยู่ เพื่อไม่ให้ใครเข้าใจผิดว่าแก้ helper ที่นั่นได้
// resources/pages/common/confirmation-modal.ts
import { ConfirmationModalComponent as Base, type ConfirmationModalOptions }
from '@sellsuki-org/sellsuki-components-test';
import { CCS3_CONFIRMATION_MODAL_i18N } from '../../../variables/i18n/component';
import type { Page, Locator } from '@playwright/test';
const i18n = CCS3_CONFIRMATION_MODAL_i18N[process.env.LANGUAGE || 'th'];
// logic อยู่ที่ package / i18n อยู่ที่ repo — ตามกฎ "component ห้าม import i18n"
export class ConfirmationModalComponent extends Base {
constructor(target: Page | Locator, options: ConfirmationModalOptions = {}) {
super(target, {
title: i18n.confirmation_modal_title,
content: i18n.confirmation_modal_content,
cancelButton: i18n.confirmation_modal_cancel_button,
confirmButton: i18n.confirmation_modal_exit_button,
...options,
});
}
}
new ConfirmationModalComponent(page)แบบไม่ส่ง options ยังคอมไพล์ผ่าน แต่จะเลิกเช็คข้อความเงียบ ๆ —tscจับไม่ได้ ต้องทำ wrapper นี้ก่อนเสมอ
พัฒนา
npm ci
npm run build # tsup → dist/ (ESM + CJS + .d.ts)
npm run typecheckrepo นี้ ไม่มี test ของตัวเอง — การยืนยันว่า selector ยังตรงกับ DOM จริงเกิดที่ suite ของแต่ละทีมอยู่แล้ว และ dev แจ้งล่วงหน้าเมื่อมีการแก้ component ทำให้ QA รู้ว่าต้อง recheck ส่วนไหน
⚠️ ข้อจำกัดที่ต้องรู้:
typecheckและbuildจับ selector ที่ผิดไม่ได้ เพราะ locator เป็น string (ทดสอบแล้ว — แก้toHaveAttribute('type')เป็น'variant'ทั้ง typecheck และ build ยังผ่าน) เพราะฉะนั้นเวลาแก้ helper ให้รัน suite ของ repo ที่ใช้จริงอย่างน้อยหนึ่งตัวก่อน merge
ทำไมต้อง build เป็น dual ESM + CJS
- Playwright ไม่ transpile ไฟล์ใน
node_modules→ ship.tsดิบไม่ได้ - e2e repo ปลายทางไม่มี
"type": "module"→ test ถูกโหลดแบบ CJS → ESM-only เสี่ยงพัง
CI
มี job เดียว — publish_npm (มาจาก shared pipeline) ทำงานเมื่อ push tag vX.Y.Z
รัน npm ci → npm version <tag> → npm publish ขึ้น registry.npmjs.org
build ถูกรันโดย prepublishOnly ตอน publish อยู่แล้ว → build พัง = publish ไม่ออก
Release
version ใน package.json ปล่อยเป็น 0.0.0 — CI เขียนทับจาก tag (npm version $CI_COMMIT_TAG)
git tag v1.0.0
git push origin v1.0.0tag ต้องตรง
^v\d{1,2}\.\d{1,2}\.\d{1,2}$— prerelease อย่างv1.0.0-beta.1pipeline จะไม่ทำงาน
