--- name: qa-clinicpro description: تست QA اپلیکیشن ClinicPro مثل یک کاربر واقعی — ابتدا ساخت همهٔ نقش‌ها و پروفایل‌های کامل (پزشک مستقل، پزشک عضو کلینیک، کلینیک، منشی، نماینده، بیمار، …) و تعیین ماتریس سطح دسترسی، سپس تست ماتریس دسترسی با تک‌تک آن‌ها. هر مانعی سر راه تست را مثل یک دولوپر ارشد Symfony/React خودش رفع می‌کند و تست را ادامه می‌دهد. اجرای اپ، ورود با هر نقش، پیمایش صفحات پنل ادمین، اسکرین‌شات، کشف خطاهای کنسول و شبکه، تست UI/UX و RTL، تست دسترسی نقش‌ها (authz)، تست قرارداد API و اندازه‌گیری کارایی، و تولید Bug Report. Use when asked to QA, test, smoke-test, find bugs in, screenshot, or verify ClinicPro's admin panel or API — «تست کن»، «باگ پیدا کن»، «QA کن»، «این صفحه را بررسی کن». --- # QA ClinicPro ClinicPro = بک‌اند Symfony 7.4 + یک **SPA کلاینت‌ساید React 19** که از `/admin/*` سرو می‌شود. یعنی `curl` و فلگ `--screenshot` کروم به درد نمی‌خورند — هر دو روی فرم لاگین می‌نشینند، چون JWT در `localStorage['clinicpro-auth']` است. درایور این skill آن کار را انجام می‌دهد: با API لاگین می‌کند، `localStorage` را seed می‌کند، بعد ناوبری می‌کند و **خطاهای کنسول، درخواست‌های شکست‌خورده، مسیری که واقعاً روی آن فرود آمده، و اسکرین‌شات** را گزارش می‌دهد — با CDP روی `WebSocket` نیتیو Node 22، **بدون هیچ وابستگی npm** (نه playwright، نه puppeteer). مسیرها نسبت به `clinicpro/` هستند. ## پیش‌نیازها هیچ نصبی لازم نیست. فقط این دو: ```bash ddev describe | head -3 # باید بالا باشد: https://clinic-pro.ddev.site ls "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ``` کروم جای دیگری است؟ `CHROME_BIN` را ست کن. بک‌اند جای دیگری است؟ `CLINICPRO_BASE`. ## کاربران تست ⚠ **`TEST_USERS.md` منسوخ است** — هیچ‌کدام از کاربرانش (`09100000001`, `09100100000`, …) در دیتابیس وجود ندارند و همه `ERR_AUTH_005` می‌گیرند. اسکریپت‌های `create_test_users.php` و `seed_realistic_data.php` هم که آن فایل ارجاع می‌دهد در ریپو نیستند. پرسوناهای QA در `ROLES` داخل درایور تعریف شده‌اند. **واحد کار «پرسونا» است، نه `ROLE_*`** — پزشک مستقل و پزشک عضو کلینیک هر دو `ROLE_DOCTOR` دارند ولی دادهٔ متفاوتی می‌بینند، پس هرکدام یک ردیف جداگانه‌اند. | پرسونا | موبایل | پسورد | نقش‌ها | تمایز | |---|---|---|---|---| | `admin` | `09120671756` | `QaTest@1234` | `ROLE_ADMIN` | — | | `clinic` | `09127000000` | `QaTest@1234` | `ROLE_CLINIC` | مالک کلینیک | | `secretary` | `09123456778` | `QaTest@1234` | `ROLE_SECRETARY` | منشیِ یک پزشک | | `doctor` | `09390039833` | `09390039833` | `ROLE_DOCTOR` | حساب قدیمی، وضعیت عضویتش نامعلوم | | `representation` | `09124000001` | `09124000001` | `ROLE_REPRESENTATION` | نماینده شهر | | `doctor_solo` | `09129000001` | `QaTest@1234` | `ROLE_DOCTOR` | **پزشک مستقل** — مطب شخصی، بدون کلینیک | | `doctor_member` | `09129000002` | `QaTest@1234` | `ROLE_DOCTOR` | پزشک **عضو کلینیک** | | `clinic_doctor` | `09129000003` | `QaTest@1234` | `ROLE_CLINIC`+`ROLE_DOCTOR` | چندنقشی | | `secretary_clinic` | `09129000004` | `QaTest@1234` | `ROLE_SECRETARY` | منشیِ کلینیک (نه پزشک) | | `unclaimed_doctor` | `09129000005` | `QaTest@1234` | `ROLE_UNCLAIMED_DOCTOR` | پروفایل ایمپورت‌شدهٔ تصاحب‌نشده | | `patient` | `09129000006` | `QaTest@1234` | `ROLE_USER` | کاربر عادی سایت | | `importer` | `09129000007` | `QaTest@1234` | `ROLE_IMPORTER` | — | پنج ردیف اول موجودند. **هفت ردیف آخر تا وقتی Phase 0 اجرا نشده وجود ندارند** و `driver.mjs roles` برایشان `✗` می‌دهد — این دقیقاً چک آمادگی است. اگر DB ریست شد، پسورد پنج‌تای اول را دوباره ست کن: ```bash ddev exec php bin/console security:hash-password 'QaTest@1234' # هش خروجی را در این کوئری بگذار: ddev mysql -e "UPDATE users SET password_hash='<هش>' \ WHERE mobile_number IN ('09120671756','09127000000','09123456778');" ``` اعتبارسنجی همه نقش‌ها: ```bash node .claude/skills/qa-clinicpro/driver.mjs roles ``` خروجی واقعی: ``` admin 09120671756 ROLE_USER,ROLE_ADMIN token 15min clinic 09127000000 ROLE_USER,ROLE_CLINIC token 15min secretary 09123456778 ROLE_USER,ROLE_SECRETARY token 15min doctor 09390039833 ROLE_USER,ROLE_DOCTOR token 15min representation 09124000001 ROLE_USER,ROLE_REPRESENTATION token 15min ``` می‌توانی به‌جای نام نقش، `--as "0912xxxxxxx:password"` هم بدهی. --- ## مسیر اجرا (agent path) ### ۰. Phase 0 — ساخت نقش‌ها، پروفایل‌ها و ماتریس دسترسی (اجباری، قبل از هر تست) هیچ تستی را قبل از تمام‌شدن این فاز شروع نکن. خروجی این فاز سه چیز است: **همهٔ پرسوناها موجود** · **پروفایل هرکدام کامل** · **ماتریس دسترسی مکتوب**. **۰.۱ — کشف نقش‌ها.** لیست بالا را دوباره از روی کد بساز، به آن استناد نکن؛ ممکن است نقشی اضافه شده باشد: ```bash grep -rhoE "ROLE_[A-Z_]+" src/ assets/admin/ config/ | sort -u ddev mysql -e "SELECT roles, COUNT(*) c FROM users GROUP BY roles ORDER BY c DESC;" grep -n "role_hierarchy" -A 10 config/packages/security.yaml ``` هر نقشی که در کد هست و در جدول پرسوناها نیست را به `ROLES` در `driver.mjs` اضافه کن. **۰.۲ — چک آمادگی.** ببین کدام پرسونا هنوز نیست: ```bash node .claude/skills/qa-clinicpro/driver.mjs roles ``` **۰.۳ — ساخت پرسوناهای ناموجود.** برای هرکدام، **اول مسیر واقعی ساخت را در خود اپ پیدا کن** و از همان استفاده کن — دست‌کاری مستقیم SQL پروفایل ناقص می‌سازد و تست را دروغین می‌کند. به این ترتیب بگرد: ```bash ls src/*/Command/ # آیا کامند کنسولی برای ساخت کاربر هست؟ grep -rn "IsGranted" src/Admin/Controller/ # اندپوینت‌های ادمینِ ساخت کاربر sed -n '1,80p' docs/api/admin.md ``` فقط برای چیزی که هیچ مسیر اپلیکیشنی ندارد (مثلاً ست‌کردن `ROLE_IMPORTER` یا ساختن `ROLE_UNCLAIMED_DOCTOR`) به `ddev mysql` برگرد، و در گزارش بنویس که کدام پرسونا دستی ساخته شد. **ترتیب ساخت مهم است** — وابستگی دارند: ``` کلینیک → doctor_member (عضو همان کلینیک) → secretary_clinic (منشیِ همان کلینیک) پزشک → secretary (منشیِ همان پزشک) ``` **۰.۴ — کامل‌کردن پروفایل.** یک حسابِ بدون پروفایل، صفحات را خالی نشان می‌دهد و باگ‌های واقعی را پنهان می‌کند. برای هر پرسونا این‌ها باید پر باشند: | پرسونا | حداقل پروفایل لازم | |---|---| | `doctor_solo` / `doctor_member` / `clinic_doctor` | نام، تخصص، آدرس مطب، برنامهٔ کاری هفتگی، حداقل یک خدمت با تعرفه، حداقل یک بیمه | | `clinic` | نام کلینیک، شهر، آدرس، حداقل یک پزشک عضو، حداقل یک خدمت | | `secretary` / `secretary_clinic` | اتصال به پزشک/کلینیک + سطح دسترسی‌اش | | `representation` | شهر تخصیص‌یافته | | `patient` | نام، و حداقل یک نوبت رزروشده (برای اینکه صفحات خالی نباشند) | | `unclaimed_doctor` | پروفایل پزشک بدون کاربرِ تصاحب‌کننده | بعد از ساخت، پرشدن را تأیید کن — نه با حدس، با درخواست: ```bash node .claude/skills/qa-clinicpro/driver.mjs api GET /api/v1/doctor/profile --as doctor_solo ``` **۰.۵ — تعیین سطح دسترسی.** ماتریس را از کد دربیاور، نه از ذهنت: ```bash grep -n "RoleRoute\|allowedRoles\|element=" assets/admin/App.tsx # مسیرهای فرانت grep -rn "IsGranted" src/*/Controller/ | sed 's/.*IsGranted(//' # گاردهای بک‌اند ``` از این دو، جدول `مسیر → نقش‌های مجاز` را بساز و در گزارش بیاور. بعد برای هر اندپوینت حساس با `authz` (بخش ۳) تأییدش کن. **اختلاف بین ماتریسِ کد و خروجی `authz` = باگ**، حتی اگر خروجی `authz` سخت‌گیرانه‌تر باشد. **۰.۶ — دروازهٔ خروج.** تا وقتی `roles` برای همهٔ پرسوناها توکن برمی‌گرداند و ماتریس نوشته شده، به فاز بعد نرو. اگر پرسونایی ساخته نشد، طبق بخش «وقتی به مانع خوردی» خودت رفعش کن؛ رها کردنش یعنی آن نقش اصلاً تست نشده. ### ۱. بازدید از صفحه — اسکرین‌شات + خطاها ```bash node .claude/skills/qa-clinicpro/driver.mjs visit \ "https://clinic-pro.ddev.site/admin/dashboard" --as admin --out /tmp/qa-dash.png ``` ``` ✓ screenshot /tmp/qa-dash.png (1440x900, as admin) LANDING (none) CONSOLE ERRORS (none) NETWORK FAILURES (none) ``` **بعد حتماً تصویر را با ابزار Read باز کن و نگاه کن.** نیمی از باگ‌های UI فقط دیدنی‌اند، نه لاگ‌شدنی — همان یک اسکرین‌شات داشبورد دو باگ i18n لو داد (پایین را ببین). فلگ‌ها: `--w 1440 --h 900` (ویوپورت)، `--wait 4000` (ms صبر برای رندر)، `--full` (کل صفحه). **موبایل را جدا تست کن** — پنل RTL و پرجدول است و بیشتر مشکلات آنجاست: ```bash node .claude/skills/qa-clinicpro/driver.mjs visit \ "https://clinic-pro.ddev.site/admin/dashboard" --as admin --w 390 --h 844 --out /tmp/qa-m.png ``` بخش `LANDING` دو حالتی را می‌گیرد که اسکرین‌شات پنهان می‌کند: ``` ⚠ WRONG PAGE: asked /admin/users, landed /admin/dashboard — role likely lacks access (RoleRoute in App.tsx) ``` ### ۲. آدیت UI/UX و RTL ```bash node .claude/skills/qa-clinicpro/driver.mjs ux \ "https://clinic-pro.ddev.site/admin/dashboard" --as admin --w 390 --h 844 ``` ``` UX FINDINGS (390x844, as admin) 5 tap target(s) under 36px on a mobile viewport ``` چک‌ها: RTL نبودن ریشه، `lang` غلط، سرریز افقی، رقم لاتین داخل متن فارسی، تارگت لمسی زیر ۳۶px، `` بدون alt، فیلد بدون label، `id` تکراری، جدول خالی بدون empty-state، و `