estelam ticket, varizha , others

This commit is contained in:
javad committed 2026-10-13 15:48:24 +03:30
1 parent 35a1ec1794
commit 6b546aad76
71 files changed
+2865 -191

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
from pathlib import Path
from django.urls import reverse
from playwright.sync_api import expect
from tests.e2e.pages.base import BasePage
class DispatchWorkspacePage(BasePage):
@property
def review_dialog(self):
return self.page.get_by_role("dialog", name="بررسی نهایی ارسال گروهی")
def _activate(self, control, destination, *, hidden=False):
control.click()
if hidden:
expect(destination).to_be_hidden()
else:
expect(destination).to_be_visible()
def open_composer(self):
self.goto(reverse("staff_tickets:dispatch_create"))
self.page.get_by_role("tab", name="ارسال جدید").wait_for(state="visible")
def open_history(self):
self.page.get_by_role("tab", name="سوابق ارسال").click()
def return_to_composer(self):
self.page.get_by_role("tab", name="ارسال جدید").click()
def compose(self, unit, group, category, subject, body):
self.page.get_by_label("واحد ارسال‌کننده").select_option(str(unit.pk))
self.page.get_by_label("گروه مخاطبان", exact=False).select_option(str(group.pk))
self.page.get_by_label("دسته‌بندی", exact=False).select_option(str(category.pk))
self.page.get_by_label("موضوع استعلام یا پیام", exact=False).fill(subject)
self.page.get_by_label("متن پیام", exact=False).fill(body)
def review(self):
self._activate(self.page.get_by_role("button", name="بررسی و ادامه"), self.review_dialog)
def cancel_review(self):
self._activate(self.review_dialog.get_by_role("button", name="بازگشت و ویرایش"), self.review_dialog, hidden=True)
def send(self):
self._activate(
self.review_dialog.get_by_role("button", name="تأیید و ارسال گروهی"),
self.page.get_by_role("heading", name="وضعیت پیامک، مشاهده و پاسخ"),
)
def open_report(self, dispatch):
self.goto(reverse("staff_tickets:dispatch_detail", args=[dispatch.public_id]))
def filter_recipients(self, label):
self.page.get_by_label("نمایش مخاطبان").select_option(label=label)
def search_recipient(self, text):
self.page.get_by_role("searchbox", name="جستجوی مخاطب").fill(text)
def reset_recipients(self):
self.page.get_by_role("button", name="پاک کردن فیلتر", exact=True).click()
def visible_recipients(self):
return self.page.locator("[data-recipient-row]:visible")
def open_conversation(self):
self.page.locator(".dispatch-reply-link").first.click()
def review_opening(self):
self._activate(self.page.get_by_role("link", name="بازگشایی همه پاسخ‌ها", exact=True),
self.page.get_by_role("heading", name="تأیید بازگشایی همه پاسخ‌ها"))
def confirm_opening(self):
self._activate(self.page.get_by_role("button", name="تأیید و بازگشایی همه پاسخ‌ها"),
self.page.get_by_role("heading", name="پاسخ‌ها بازگشایی شده‌اند"))
def choose_response_mode(self, label):
self.page.get_by_role("radio", name=label, exact=True).check()
@property
def opening_sms(self):
return self.page.get_by_role("checkbox", name="اطلاع‌رسانی بازگشایی با پیامک", exact=True)
def expect_fits_viewport(self):
assert self.page.evaluate("document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1")
def save_screenshot(self, name):
directory = Path("test-results/dispatch-design")
directory.mkdir(parents=True, exist_ok=True)
self.page.screenshot(path=str(directory / name), full_page=True)
+7
View File
@@ -5,6 +5,13 @@ from .base import BasePage
class StaffPaymentsPage(BasePage):
@property
def daily_summary(self):
return self.page.get_by_role('region', name='وضعیت امروز')
def open_summary_definitions(self):
self.daily_summary.locator('summary').click()
def open_desk(self, parameters=''):
self.goto(reverse('staff_payments:desk') + parameters)
@@ -0,0 +1,105 @@
import pytest
from playwright.sync_api import expect
from apps.messaging.models import Message
from apps.notifications.models import SMSTemplate
from apps.tickets.services import dispatch_group_tickets
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.dispatches import DispatchWorkspacePage
from tests.e2e.test_dispatch_workspace_e2e import dispatch_case, PASSWORD
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
@pytest.mark.parametrize("width", [1440, 390, 320])
def test_sealed_report_requires_confirmation_and_opens_answers_together(live_server, browser_page, dispatch_case, width):
staff, unit, group, category, _, customers = dispatch_case
# Transactional live-server tests flush data migrations between tests.
SMSTemplate.objects.create(unit=unit, event_type="DISPATCH_RESPONSES_OPENED", title="بازگشایی", body="متن دقیق پیامک بازگشایی برای آزمون")
dispatch, tickets = dispatch_group_tickets(user=staff, unit=unit, contact_group=group,
category=category, subject="استعلام محرمانه مرورگر", body="پیشنهاد خود را ثبت کنید.")
answer = "پیشنهاد محرمانه مخاطب اول " * 12 + "پایان کامل پیشنهاد"
Message.objects.create(ticket=tickets[0], author=customers[0], body=answer)
second_answer = Message.objects.create(ticket=tickets[0], author=customers[0], body="توضیح تکمیلی پیشنهاد اول")
Message.objects.create(ticket=tickets[1], author=customers[1], body="پیشنهاد مخاطب دوم")
with browser_page() as page:
page.set_viewport_size({"width": width, "height": 900})
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
workspace = DispatchWorkspacePage(page, live_server.url)
workspace.open_report(dispatch)
expect(page.get_by_role("heading", name="پاسخ‌ها هنوز بازگشایی نشده‌اند")).to_be_visible()
expect(page.locator("body")).not_to_contain_text(answer)
expect(page.locator('[data-recipient-row] a[href*="/staff/tickets/"]')).to_have_count(0)
workspace.filter_recipients("دارای پاسخ")
expect(workspace.visible_recipients()).to_have_count(2)
expect(workspace.visible_recipients().first).to_contain_text("پاسخ داده است: بله")
workspace.expect_fits_viewport()
workspace.save_screenshot(f"sealed-report-{width}.png")
workspace.review_opening()
expect(page.locator(".opening-sms-preview")).to_have_text("متن دقیق پیامک بازگشایی برای آزمون")
expect(page.locator("body")).not_to_contain_text(answer)
workspace.expect_fits_viewport()
workspace.save_screenshot(f"opening-confirm-{width}.png")
workspace.confirm_opening()
expect(page.locator("body")).to_contain_text(answer)
expect(page.locator("body")).to_contain_text(second_answer.body)
expect(page.locator("body")).to_contain_text("پیشنهاد مخاطب دوم")
expect(page.locator(".recipient-reply")).to_have_count(3)
expect(page.locator('[data-recipient-row] a[href*="/staff/tickets/"]')).to_have_count(0)
from apps.core.templatetags.jalali import jalali_datetime_precise
expect(page.locator(".recipient-reply time").nth(1)).to_have_text(jalali_datetime_precise(second_answer.created_at))
workspace.expect_fits_viewport()
workspace.save_screenshot(f"opened-report-{width}.png")
def test_composer_defaults_to_sealed_and_disables_sms_in_individual_mode(live_server, browser_page, dispatch_case):
staff, unit, group, category, _, _ = dispatch_case
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
workspace = DispatchWorkspacePage(page, live_server.url)
workspace.open_composer()
workspace.compose(unit, group, category, "پیش‌فرض پاسخ محرمانه", "متن درخواست")
expect(page.get_by_role("radio", name="بازگشایی یکجا", exact=True)).to_be_checked()
expect(workspace.opening_sms).to_be_checked()
workspace.review()
expect(workspace.review_dialog.locator('[data-review-inquiry-recommendation]')).to_be_hidden()
workspace.cancel_review()
workspace.choose_response_mode("مشاهده جداگانه")
expect(workspace.opening_sms).to_be_disabled()
expect(workspace.opening_sms).not_to_be_checked()
workspace.review()
expect(workspace.review_dialog).to_contain_text("مشاهده جداگانه؛ پیامک بازگشایی: غیرفعال")
expect(workspace.review_dialog.locator('[data-review-inquiry-recommendation]')).to_be_visible()
expect(workspace.review_dialog).to_contain_text("برای تمامی استعلام‌گیری‌ها توصیه می‌کنیم")
workspace.cancel_review()
workspace.choose_response_mode("بازگشایی یکجا")
expect(workspace.opening_sms).to_be_checked()
workspace.opening_sms.uncheck()
workspace.choose_response_mode("مشاهده جداگانه")
workspace.choose_response_mode("بازگشایی یکجا")
expect(workspace.opening_sms).not_to_be_checked()
workspace.review()
expect(workspace.review_dialog.locator('[data-review-inquiry-recommendation]')).to_be_visible()
workspace.save_screenshot("review-inquiry-recommendation.png")
@pytest.mark.parametrize("count", [15, 16])
def test_composer_enforces_opening_sms_recipient_cap(live_server, browser_page, dispatch_case, count):
from tests.test_organizational_ticketing import make_contact
staff, unit, group, category, _, _ = dispatch_case
for i in range(2, count):
make_contact(f"browser-limit-{i}", f"09122100{i:03}").customer_profile.groups.add(group)
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
workspace = DispatchWorkspacePage(page, live_server.url)
workspace.open_composer()
workspace.compose(unit, group, category, "آزمون سقف پیامک مرورگر", "متن استعلام")
if count == 15:
expect(workspace.opening_sms).to_be_enabled()
expect(workspace.opening_sms).to_be_checked()
else:
expect(workspace.opening_sms).to_be_disabled()
expect(workspace.opening_sms).not_to_be_checked()
expect(page.locator('[data-opening-sms-hint]')).to_contain_text("بیش از ۱۵")
workspace.review()
expect(workspace.review_dialog.locator('[data-review-inquiry-limit]')).to_be_visible()
+108
View File
@@ -0,0 +1,108 @@
import pytest
from django.contrib.auth.models import Permission
from django.urls import reverse
from django.utils import timezone
from playwright.sync_api import expect
from apps.accounts.models import TicketMagicLink, User
from apps.customers.models import ContactGroup, CustomerProfile
from apps.messaging.models import Message
from apps.notifications.models import NotificationJob, SMSTemplate
from apps.staff.models import OrganizationalUnit, StaffProfile
from apps.tickets.models import TicketCategory
from apps.tickets.services import create_group_dispatch
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.dispatches import DispatchWorkspacePage
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
PASSWORD = "Dispatch-E2E-safe-2026"
@pytest.fixture
def dispatch_case():
unit = OrganizationalUnit.objects.create(name="فروش رابط گروهی", slug="dispatch-ui")
unit.permissions.add(*Permission.objects.filter(content_type__app_label="tickets", codename__in=("view_ticket", "change_ticket", "dispatch_group_tickets")))
staff = User.objects.create_user(username="dispatch-ui-agent", password=PASSWORD, account_type="STAFF")
StaffProfile.objects.create(user=staff, display_name="کارشناس ارسال", primary_unit=unit)
group = ContactGroup.objects.create(unit=unit, name="مخاطبان منتخب فروش")
empty_group = ContactGroup.objects.create(unit=unit, name="گروه بدون مخاطب")
category = TicketCategory.objects.create(unit=unit, title="هماهنگی فروش", slug="dispatch-ui-category")
customers = []
for index, name in enumerate(("مخاطب پاسخ‌دهنده", "مخاطب بدون پاسخ")):
customer = User.objects.create_user(username=f"dispatch-ui-customer-{index}", mobile=f"0912000980{index}", account_type="CUSTOMER")
CustomerProfile.objects.create(user=customer, first_name=name).groups.add(group)
customers.append(customer)
return staff, unit, group, category, empty_group, customers
@pytest.mark.parametrize("width", [1440, 390, 320])
def test_review_preserves_draft_and_requires_confirmation_before_group_send(live_server, browser_page, dispatch_case, width):
staff, unit, group, category, _, _ = dispatch_case
subject = "هماهنگی زمان تحویل گروهی"
body = "لطفاً زمان پیشنهادی تحویل را در گفت‌وگوی خصوصی اعلام کنید."
with browser_page() as page:
page.set_viewport_size({"width": width, "height": 900 if width == 1440 else 844})
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
workspace = DispatchWorkspacePage(page, live_server.url)
workspace.open_composer()
workspace.compose(unit, group, category, subject, body)
expect(page.get_by_label("خلاصه ارسال جدید")).to_contain_text("۲ نفر")
workspace.open_history()
expect(page.get_by_text("هنوز ارسال گروهی ثبت نشده است")).to_be_visible()
workspace.return_to_composer()
expect(page.get_by_label("متن پیام", exact=False)).to_have_value(body)
workspace.review()
dialog = page.get_by_role("dialog")
expect(dialog).to_contain_text("۲ نفر")
expect(dialog).to_contain_text(subject)
workspace.cancel_review()
expect(page.get_by_role("button", name="بررسی و ادامه")).to_be_enabled()
workspace.save_screenshot(f"compose-{width}.png")
workspace.review()
workspace.save_screenshot(f"review-{width}.png")
workspace.send()
expect(page.locator("[data-recipient-row]")).to_have_count(2)
expect(page.locator(".sms-state-card").first).to_contain_text("در صف ارسال")
expect(page.get_by_text("تحویل پیامک به معنی مشاهده لینک یا دریافت پاسخ نیست.")).to_be_visible()
workspace.expect_fits_viewport()
@pytest.mark.parametrize("width", [1440, 390, 320])
def test_report_filters_search_and_private_reply_navigation(live_server, browser_page, dispatch_case, width):
staff, unit, group, category, _, customers = dispatch_case
dispatch, tickets = create_group_dispatch(user=staff, unit=unit, contact_group=group, category=category, subject="پیگیری پاسخ مخاطبان منتخب", body="پیام اصلی با پاسخ‌های خصوصی مستقل.", response_mode="INDIVIDUAL")
first = tickets[0]
Message.objects.create(ticket=first, author=customers[0], body="زمان پیشنهادی ما ساعت ده صبح است.")
first.last_customer_message_at = timezone.now()
first.save(update_fields=["last_customer_message_at"])
link = TicketMagicLink.issue_with_record(first, created_by=staff)[1]
link.view_count = 2
link.save(update_fields=["view_count"])
NotificationJob.objects.filter(ticket=first, event_type=SMSTemplate.Event.STAFF_TICKET_CREATED).update(status="SENT", attempts=1, delivery_status="DELIVERED")
NotificationJob.objects.filter(ticket=tickets[1], event_type=SMSTemplate.Event.STAFF_TICKET_CREATED).update(status="FAILED", attempts=1, last_error="خطای آزمایشی درگاه")
with browser_page() as page:
page.set_viewport_size({"width": width, "height": 900 if width == 1440 else 844})
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
workspace = DispatchWorkspacePage(page, live_server.url)
workspace.open_report(dispatch)
expect(workspace.visible_recipients()).to_have_count(2)
workspace.filter_recipients("دارای پاسخ")
expect(workspace.visible_recipients()).to_have_count(1)
expect(workspace.visible_recipients()).to_contain_text("زمان پیشنهادی ما ساعت ده صبح است.")
workspace.reset_recipients()
workspace.filter_recipients("پیامک نیازمند اقدام")
expect(workspace.visible_recipients()).to_have_count(1)
expect(workspace.visible_recipients()).to_contain_text("ارسال ناموفق")
# One failed attempt is not a successful first attempt.
expect(workspace.visible_recipients()).not_to_contain_text("موفق در اولین تلاش")
workspace.reset_recipients()
workspace.search_recipient("۰۹۱۲۰۰۰۹۸۰۰")
expect(workspace.visible_recipients()).to_have_count(1)
workspace.search_recipient("وجود ندارد")
expect(page.get_by_role("heading", name="مخاطبی با این شرایط پیدا نشد")).to_be_visible()
workspace.reset_recipients()
workspace.expect_fits_viewport()
workspace.save_screenshot(f"report-{width}.png")
workspace.filter_recipients("دارای پاسخ")
workspace.open_conversation()
expect(page).to_have_url(workspace.url(reverse("staff_tickets:detail", args=[first.public_id])))
@@ -0,0 +1,61 @@
import uuid
import pytest
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils import timezone
from playwright.sync_api import expect
from apps.payments import services
from apps.payments.models import MAX_AMOUNT
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.staff_payments import StaffPaymentsPage
from tests.e2e.test_payments_e2e import PASSWORD, PNG, payment_case
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
def test_daily_summary_mobile_desktop_and_filtered_empty_results(live_server, browser_page, payment_case, tmp_path):
customer, account, reviewers = payment_case
services.issue_notice(reviewers[0], {'customer': customer, 'account': account,
'amount': 100000, 'subject': 'اعلامیه آزمایشی', 'description': '', 'deposit_id': ''})
receipt = services.submit_receipt(customer, customer, {
'paid_amount': MAX_AMOUNT, 'paid_date': timezone.localdate(),
'destination_account': account, 'depositor_name': 'مشتری آزمایشی',
'submission_key': uuid.uuid4(),
'receipt_image': SimpleUploadedFile('receipt.png', PNG, content_type='image/png'),
})
services.decide_receipt(reviewers[0], receipt.public_id, 1, 'APPROVE')
services.decide_receipt(reviewers[1], receipt.public_id, 1, 'APPROVE')
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_staff(reviewers[0], PASSWORD)
workspace = StaffPaymentsPage(page, live_server.url)
workspace.open_desk()
summary = workspace.daily_summary
expect(summary).to_be_visible()
expect(summary.locator('[data-summary=submitted]')).to_contain_text('1 فیش')
expect(summary.locator('[data-summary=approved]')).to_contain_text('1 فیش')
expect(summary.locator('[data-summary=rejected]')).to_contain_text('0 فیش')
expect(summary.locator('[data-summary=pending]')).to_contain_text('0 فیش')
expect(summary.locator('[data-summary=notices]')).to_contain_text('1 اعلامیه')
amount = f'{MAX_AMOUNT:,}'
expect(summary.locator('[data-summary=approved]')).to_contain_text(amount)
for width, height in [(320, 568), (360, 640), (390, 844), (768, 1024), (1440, 900)]:
page.set_viewport_size({'width': width, 'height': height})
expect(summary).to_be_visible()
assert page.evaluate('document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1')
assert summary.evaluate('(el) => el.scrollWidth <= el.clientWidth + 1')
page.screenshot(path=tmp_path / f'payment-summary-{width}.png', full_page=True)
page.set_viewport_size({'width': 320, 'height': 568})
page.evaluate("document.documentElement.style.fontSize = '200%'")
assert summary.evaluate('(el) => el.scrollWidth <= el.clientWidth + 1')
expect(summary.locator('[data-summary=approved]')).to_contain_text(amount)
workspace.open_summary_definitions()
definitions = summary.locator('.pay-day-definitions')
for title in ['ارسالی امروز:', 'تأیید امروز:', 'رد امروز:', 'در انتظار بررسی:', 'اعلامیه امروز:', 'قواعد مشترک:']:
expect(definitions.get_by_text(title, exact=True)).to_be_visible()
expect(definitions).not_to_contain_text('امروز بر اساس ساعت تهران است')
page.evaluate("document.documentElement.style.fontSize = ''")
workspace.search('no-such-customer')
expect(page.get_by_text('موردی پیدا نشد', exact=True)).to_be_visible()
expect(summary.locator('[data-summary=approved]')).to_contain_text('1 فیش')
expect(summary.locator('[data-summary=approved]')).to_contain_text(amount)
+4
View File
@@ -16,6 +16,7 @@ from apps.staff.models import OrganizationalUnit, StaffProfile
from apps.tickets.models import TicketCategory
from apps.tickets.services import create_ticket, dispatch_group_tickets
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.dispatches import DispatchWorkspacePage
from tests.e2e.pages.tickets import TicketConversationPage
@@ -127,7 +128,10 @@ def test_staff_operations_pages_are_authorized_and_visually_styled(live_server,
expect(page).to_have_title("ردیابی E2E")
expect(page.locator(".staff-brand-copy b")).to_have_text("مرکز عملیات E2E")
expect(page.locator(".staff-developer-credit")).to_contain_text("امضای مستقل sidebar E2E")
workspace = DispatchWorkspacePage(page, live_server.url)
workspace.open_history()
expect(page.get_by_role("searchbox", name="جستجو")).to_be_visible()
workspace.return_to_composer()
expect(page.locator('link[href*="dispatches.css"]')).to_have_count(1)
expect(page.get_by_text("افزودن عکس از گالری")).to_be_visible()
expect(page.locator('label[for="dispatch-file-input"]')).to_be_visible()