Files
customerTicket/tests/e2e/test_staff_operations_ui.py
T
2026-09-17 11:08:16 +03:30

210 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import base64
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.core.models import PageTitle, SiteBranding
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_ticket, dispatch_group_tickets
from tests.e2e.pages.auth import AuthenticationPage
from tests.e2e.pages.tickets import TicketConversationPage
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
PASSWORD = "E2e-safe-password-2026"
def test_staff_operations_pages_are_authorized_and_visually_styled(live_server, browser_page):
branding = SiteBranding.load()
branding.staff_brand_title = "مرکز عملیات E2E"
branding.staff_brand_subtitle = "ارتباطات یکپارچه E2E"
branding.login_intro_eyebrow = "معرفی ورود E2E"
branding.developer_credit_text = "توسعه E2E | 09120000000"
branding.staff_sidebar_credit_text = "امضای مستقل sidebar E2E"
branding.save()
PageTitle.objects.update_or_create(route_name="staff_tickets:dispatch_create", defaults={"title": "ردیابی E2E"})
SMSTemplate.objects.update_or_create(unit=None, event_type=SMSTemplate.Event.STAFF_REPLY, defaults={"title": "الگوی E2E", "body": "{ticket_number}: {magic_link}"})
unit = OrganizationalUnit.objects.create(name="عملیات E2E", slug="operations-e2e")
permissions = {
"customers": {"view_customerprofile", "add_customerprofile", "change_customerprofile", "view_contactgroup", "add_contactgroup", "change_contactgroup"},
"tickets": {"view_ticket", "add_ticket", "dispatch_group_tickets"},
"notifications": {"view_smstemplate", "add_smstemplate", "change_smstemplate"},
"accounts": {"view_ticketmagiclink"},
}
query = None
from django.db.models import Q
for app_label, codenames in permissions.items():
part = Q(content_type__app_label=app_label, codename__in=codenames)
query = part if query is None else query | part
unit.permissions.add(*Permission.objects.filter(query))
staff = User.objects.create_user(username="e2e-operations", password=PASSWORD, account_type=User.AccountType.STAFF)
StaffProfile.objects.create(user=staff, display_name="کارشناس عملیات", primary_unit=unit)
customer = User.objects.create_user(username="e2e-carrier", mobile="09120009121", account_type=User.AccountType.CUSTOMER)
profile = CustomerProfile.objects.create(user=customer, first_name="باربری", stakeholder_type=CustomerProfile.StakeholderType.CARRIER)
group = ContactGroup.objects.create(unit=unit, name="باربری‌ها")
secondary_group = ContactGroup.objects.create(unit=unit, name="مخاطبان منتخب")
profile.groups.add(group)
category = TicketCategory.objects.create(title="استعلام E2E", slug="operations-e2e")
ticket = create_ticket(customer=customer, created_by=staff, unit=unit, category=category, subject="استعلام آزمایشی", body="قیمت را اعلام کنید")
TicketMagicLink.issue_with_record(ticket, created_by=staff)
with browser_page() as page:
page.goto(f"{live_server.url}{reverse('accounts:login')}", wait_until="domcontentloaded")
expect(page.locator(".auth-brand strong")).to_have_count(0)
expect(page.locator(".auth-intro .eyebrow")).to_have_text("معرفی ورود E2E")
expect(page.locator(".auth-footer")).to_contain_text("توسعه E2E")
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
for route, heading in (
("staff_tickets:contacts", "مخاطبان و مشتریان"),
("staff_tickets:groups", "گروه‌های مخاطبان"),
("staff_tickets:contact_create", "ثبت مخاطب بیرونی"),
("staff_tickets:sms_templates", "الگوهای پیامک واحدها"),
("staff_tickets:dispatch_create", "ارسال گروهی ایزوله"),
("staff_tickets:magic_audit", "ممیزی لینک‌های جادویی"),
):
page.goto(f"{live_server.url}{reverse(route)}", wait_until="domcontentloaded")
expect(page.get_by_role("heading", name=heading, level=1)).to_be_visible()
expect(page.locator('link[href*="operations.css"]')).to_have_count(1)
assert page.evaluate("getComputedStyle(document.querySelector('.operations-page')).display") == "grid"
assert page.evaluate("document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1")
page.goto(f"{live_server.url}{reverse('staff_tickets:dispatch_create')}", wait_until="domcontentloaded")
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")
expect(page.get_by_role("searchbox", name="جستجو")).to_be_visible()
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()
tiny_png = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=")
page.locator("#dispatch-gallery-input").set_input_files([
{"name":"dispatch-first.png","mimeType":"image/png","buffer":tiny_png},
{"name":"dispatch-second.png","mimeType":"image/png","buffer":tiny_png},
])
expect(page.locator(".dispatch-attachment-picker [data-file-preview] .file-chip")).to_have_count(2)
expect(page.locator(".dispatch-attachment-picker [data-file-preview] img")).to_have_count(2)
page.locator("#dispatch-file-input").set_input_files({"name":"dispatch-document.pdf","mimeType":"application/pdf","buffer":b"%PDF-1.4\n%%EOF"})
expect(page.locator(".dispatch-attachment-picker [data-file-preview] .file-chip")).to_have_count(3)
page.locator(".dispatch-attachment-picker [data-file-preview] .file-chip button").first.click()
expect(page.locator(".dispatch-attachment-picker [data-file-preview] .file-chip")).to_have_count(2)
assert page.locator('.dispatch-attachment-picker input[name="files"]').evaluate("input => input.files.length") == 2
page.goto(f"{live_server.url}{reverse('staff_tickets:sms_templates')}", wait_until="domcontentloaded")
expect(page.get_by_text("الگوی عمومی سیستم").first).to_be_visible()
page.goto(f"{live_server.url}{reverse('staff_tickets:contact_create')}", wait_until="domcontentloaded")
province = page.locator('select[name="province"]')
city = page.locator('select[name="city"]')
expect(province.locator("option")).not_to_have_count(1)
province.select_option(label="اصفهان")
expect(city.locator("option", has_text="کاشان")).to_have_count(1)
page.goto(f"{live_server.url}{reverse('staff_tickets:contact_edit', args=[customer.public_id])}", wait_until="domcontentloaded")
first_group = page.locator(f'input[name="groups"][value="{group.pk}"]')
second_group = page.locator(f'input[name="groups"][value="{secondary_group.pk}"]')
expect(first_group).to_be_checked()
expect(second_group).not_to_be_checked()
second_group.check()
first_group.uncheck()
expect(page.locator("[data-group-selection-count]")).to_contain_text("۱")
page.get_by_role("button", name="ذخیره مخاطب").click()
page.wait_for_url(f"**{reverse('staff_tickets:contacts')}")
page.goto(f"{live_server.url}{reverse('staff_tickets:contact_edit', args=[customer.public_id])}", wait_until="domcontentloaded")
expect(page.locator(f'input[name="groups"][value="{group.pk}"]')).not_to_be_checked()
expect(page.locator(f'input[name="groups"][value="{secondary_group.pk}"]')).to_be_checked()
page.goto(f"{live_server.url}{reverse('staff_tickets:create')}", wait_until="domcontentloaded")
page.locator("#staff-ticket-gallery-input").set_input_files([
{"name": "first.png", "mimeType": "image/png", "buffer": tiny_png},
{"name": "second.png", "mimeType": "image/png", "buffer": tiny_png},
])
expect(page.locator("[data-file-preview] .file-chip")).to_have_count(2)
expect(page.locator("[data-file-preview] .file-chip img")).to_have_count(2)
page.locator("[data-file-preview] .file-chip button").first.click()
expect(page.locator("[data-file-preview] .file-chip")).to_have_count(1)
assert page.locator('input[name="files"]').evaluate("input => input.files.length") == 1
def test_dispatch_report_presents_delivery_and_engagement_professionally(live_server, browser_page):
from apps.core.templatetags.jalali import jalali_datetime
unit=OrganizationalUnit.objects.create(name="فروش گزارش E2E",slug="dispatch-report-e2e")
unit.permissions.add(*Permission.objects.filter(content_type__app_label="tickets",codename__in=("view_ticket","dispatch_group_tickets")))
staff=User.objects.create_user(username="dispatch-report-agent",password=PASSWORD,account_type=User.AccountType.STAFF)
StaffProfile.objects.create(user=staff,display_name="کارشناس گزارش",primary_unit=unit)
customer=User.objects.create_user(username="dispatch-report-customer",mobile="09120009131",account_type=User.AccountType.CUSTOMER)
profile=CustomerProfile.objects.create(user=customer,first_name="مخاطب",last_name="آزمایشی")
group=ContactGroup.objects.create(unit=unit,name="گروه گزارش حرفه‌ای")
profile.groups.add(group)
category=TicketCategory.objects.create(unit=unit,title="استعلام گزارش",slug="dispatch-report-category-e2e")
dispatch,tickets=dispatch_group_tickets(user=staff,unit=unit,contact_group=group,category=category,subject="استعلام حرفه‌ای",body="این متن اولیه کارشناس باید بالای گزارش دیده شود.")
ticket=tickets[0]
job=NotificationJob.objects.get(ticket=ticket,event_type=SMSTemplate.Event.STAFF_TICKET_CREATED)
NotificationJob.objects.filter(pk=job.pk).update(
status=NotificationJob.Status.SENT,attempts=1,sent_at=timezone.now(),
provider_message_id="1111182358",provider_status_code="1111182358",
delivery_status=NotificationJob.DeliveryStatus.DELIVERED,delivery_status_code="5",
delivery_description="0098SMS [کد 5]: پیامک به گوشی گیرنده رسیده است.",
delivery_check_attempts=2,delivery_checked_at=timezone.now(),delivery_next_check_at=None,
)
for index in range(24):
Message.objects.create(ticket=ticket,author=staff,body=f"مکاتبه طولانی شماره {index + 1}")
links=[TicketMagicLink.issue_with_record(ticket,created_by=staff)[1] for _ in range(4)]
links[0].view_count=2; links[0].used_at=timezone.now(); links[0].save(update_fields=["view_count","used_at"])
with browser_page() as page:
AuthenticationPage(page,live_server.url).login_staff(staff,PASSWORD)
page.goto(f"{live_server.url}{reverse('staff_tickets:dispatch_detail',args=[dispatch.public_id])}",wait_until="domcontentloaded")
expect(page.get_by_role("heading",name="استعلام حرفه‌ای",level=1)).to_be_visible()
expect(page.get_by_text("این متن اولیه کارشناس باید بالای گزارش دیده شود.")).to_be_visible()
expect(page.locator(".sms-state-card")).to_contain_text("تحویل به گوشی تأیید شد")
expect(page.locator(".engagement-state")).to_contain_text("2 بازدید")
expect(page.locator('[role="progressbar"]')).to_have_attribute("aria-valuenow","100")
expect(page.locator(".delivery-progress-copy")).to_contain_text("100٪")
assert float(page.locator(".sms-state-card strong").evaluate("node => parseFloat(getComputedStyle(node).fontSize)")) >= 12
assert float(page.locator(".delivery-progress-track").evaluate("node => parseFloat(getComputedStyle(node).height)")) <= 8
created_text=page.locator(".dispatch-meta-list time").inner_text()
assert created_text == jalali_datetime(dispatch.created_at)
assert "0 + 0" not in page.locator("body").inner_text()
page.get_by_text("جزئیات فنی و رهگیری").click()
expect(page.get_by_text("1111182358")).to_be_visible()
expect(page.get_by_text("موفق در اولین تلاش")).to_be_visible()
assert page.evaluate("document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1")
page.set_viewport_size({"width":390,"height":844})
expect(page.locator(".dispatch-detail-table tbody tr").first).to_be_visible()
assert page.evaluate("document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1")
page.goto(f"{live_server.url}{reverse('staff_tickets:detail',args=[ticket.public_id])}", wait_until="domcontentloaded")
expect(page.locator(".ticket-dispatch-context")).to_contain_text("استعلام حرفه‌ای")
dismiss_context = page.get_by_role("button", name="بستن پیام ارسال گروهی")
expect(dismiss_context).to_be_visible()
launch = page.locator(".composer-launch")
expect(launch).to_be_visible()
box = launch.bounding_box()
assert box is not None
assert box["x"] >= 0 and box["x"] + box["width"] <= 390
assert box["y"] >= 0 and box["y"] + box["height"] <= 844
assert page.evaluate("document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1")
conversation=TicketConversationPage(page,live_server.url)
conversation.expect_latest_message_above_composer()
launch.click()
close_composer = page.get_by_role("button", name="بستن", exact=True)
expect(close_composer).to_be_hidden()
expect(page.locator('form[data-ticket-composer] textarea')).to_be_focused()
page.set_viewport_size({"width":390,"height":430})
expect(page.locator("[data-composer-shell]")).to_have_attribute("data-keyboard-open","true")
page.set_viewport_size({"width":390,"height":844})
expect(page.locator("[data-composer-shell].is-collapsed")).to_be_visible()
conversation.expect_latest_message_above_composer()
dismiss_context.click()
expect(page.locator(".ticket-dispatch-context")).to_have_count(0)