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

159 lines
8.3 KiB
Python

import pytest
from django.urls import reverse
from playwright.sync_api import expect
from apps.accounts.models import TicketMagicLink, User
from apps.core.models import SiteBranding
from apps.customers.models import CustomerProfile
from apps.messaging.models import Message
from apps.staff.models import StaffProfile
from apps.tickets.models import TicketCategory
from apps.tickets.services import create_ticket
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 assert_workspace_fits_visual_viewport(page):
metrics = page.evaluate(
"""() => {
const box = document.querySelector('.ticket-workspace').getBoundingClientRect();
const viewportHeight = window.visualViewport?.height || window.innerHeight;
return {
top: box.top,
bottom: box.bottom,
viewportHeight,
pageWidth: document.documentElement.scrollWidth,
viewportWidth: document.documentElement.clientWidth,
};
}"""
)
assert metrics["top"] >= -1
assert metrics["bottom"] <= metrics["viewportHeight"] + 1
assert metrics["pageWidth"] <= metrics["viewportWidth"] + 1
def assert_reply_input_is_visible(page):
metrics = page.evaluate(
"""() => {
const input = document.querySelector('form[data-ticket-composer] textarea');
const composer = document.querySelector('[data-composer-shell]');
const inputBox = input.getBoundingClientRect();
const composerBox = composer.getBoundingClientRect();
const viewportTop = window.visualViewport?.offsetTop || 0;
const viewportBottom = viewportTop + (window.visualViewport?.height || window.innerHeight);
const visibleTop = Math.max(inputBox.top, composerBox.top, viewportTop);
const visibleBottom = Math.min(inputBox.bottom, composerBox.bottom, viewportBottom);
return {visibleHeight: Math.max(0, visibleBottom - visibleTop), inputHeight: inputBox.height};
}"""
)
assert metrics["visibleHeight"] >= min(64, metrics["inputHeight"] * 0.6)
def assert_compact_composer_controls(page):
metrics = page.evaluate(
"""() => {
const input = document.querySelector('form[data-ticket-composer] textarea').getBoundingClientRect();
const jump = document.querySelector('[data-scroll-to-submit]').getBoundingClientRect();
const pickers = [...document.querySelectorAll('.media-picker-button')].map(node => node.getBoundingClientRect());
return {
inputBottom: input.bottom,
jumpBottom: jump.bottom,
pickerTopDifference: Math.abs(pickers[0].top - pickers[1].top),
pickersOverlap: !(pickers[0].right <= pickers[1].left || pickers[1].right <= pickers[0].left),
};
}"""
)
assert abs(metrics["inputBottom"] - metrics["jumpBottom"]) <= 8
assert metrics["pickerTopDifference"] <= 2
assert not metrics["pickersOverlap"]
def assert_textarea_grows_with_content(page):
reply = page.locator('form[data-ticket-composer] textarea')
reply.fill("پاسخ کوتاه")
compact_height = reply.bounding_box()["height"]
reply.fill("خط اول\nخط دوم\nخط سوم\nخط چهارم\nخط پنجم\nخط ششم\nخط هفتم")
grown_height = reply.bounding_box()["height"]
assert compact_height <= 52
assert compact_height < grown_height <= 145
def test_magic_link_mobile_viewport_keeps_composer_messages_and_credit_visible(live_server, browser_page):
branding = SiteBranding.load()
branding.developer_credit_text = "امضای مقدس توسعه‌دهنده E2E"
branding.save()
category = TicketCategory.objects.create(title="موبایل لینک امن", slug="magic-mobile-viewport")
customer = User.objects.create_user(username="magic-mobile", mobile="09120009101", account_type=User.AccountType.CUSTOMER)
CustomerProfile.objects.create(user=customer, first_name="مشتری", last_name="لینک امن")
staff = User.objects.create_user(username="magic-mobile-staff", mobile="09120009102", account_type=User.AccountType.STAFF)
StaffProfile.objects.create(user=staff, display_name="کارشناس لینک امن")
ticket = create_ticket(customer=customer, category=category, subject="بررسی موبایل لینک امن", body="پیام آغازین")
for index in range(12):
Message.objects.create(ticket=ticket, author=staff, body=f"پاسخ کارشناس شماره {index + 1}")
token = TicketMagicLink.issue(ticket)
with browser_page() as page:
page.set_viewport_size({"width": 390, "height": 844})
page.goto(f"{live_server.url}{reverse('accounts:magic', args=[token])}", wait_until="domcontentloaded")
conversation = TicketConversationPage(page, live_server.url)
credit = page.locator(".ticket-detail-credit")
expect(credit).to_be_visible()
expect(credit).to_contain_text("امضای مقدس توسعه‌دهنده E2E")
assert_workspace_fits_visual_viewport(page)
conversation.expect_latest_message_above_composer()
conversation.open_composer()
assert_compact_composer_controls(page)
page.set_viewport_size({"width": 390, "height": 430})
expect(page.locator('form[data-ticket-composer] textarea')).to_be_focused()
expect(page.locator("[data-composer-shell]")).to_have_attribute("data-keyboard-open", "true")
assert_textarea_grows_with_content(page)
assert_reply_input_is_visible(page)
expect(page.locator("[data-composer-close]")).to_be_hidden()
expect(credit).to_be_visible()
assert_workspace_fits_visual_viewport(page)
page.set_viewport_size({"width": 390, "height": 844})
expect(page.locator("[data-composer-shell].is-collapsed")).to_be_visible()
expect(credit).to_be_visible()
assert_workspace_fits_visual_viewport(page)
conversation.expect_latest_message_above_composer()
def test_staff_ticket_mobile_viewport_survives_open_close_and_rotation(live_server, browser_page):
category = TicketCategory.objects.create(title="موبایل کارشناس", slug="staff-mobile-viewport")
customer = User.objects.create_user(username="staff-mobile-customer", mobile="09120009103", account_type=User.AccountType.CUSTOMER)
CustomerProfile.objects.create(user=customer, first_name="مشتری", last_name="پنل کارشناس")
staff = User.objects.create_user(username="staff-mobile-user", mobile="09120009104", password=PASSWORD, account_type=User.AccountType.STAFF)
StaffProfile.objects.create(user=staff, display_name="کارشناس موبایل")
ticket = create_ticket(customer=customer, category=category, subject="بررسی موبایل پنل کارشناس", selected_expert=staff, body="شرح درخواست مشتری")
for index in range(12):
Message.objects.create(ticket=ticket, author=customer, body=f"پیام مشتری شماره {index + 1}")
with browser_page() as page:
page.set_viewport_size({"width": 390, "height": 844})
AuthenticationPage(page, live_server.url).login_staff(staff, PASSWORD)
conversation = TicketConversationPage(page, live_server.url)
conversation.goto(reverse("staff_tickets:detail", args=[ticket.public_id]))
expect(page.get_by_role("button", name="ثبت اقدام جدید در تیکت")).to_be_visible()
assert_workspace_fits_visual_viewport(page)
conversation.expect_latest_message_above_composer()
conversation.open_composer()
page.set_viewport_size({"width": 844, "height": 390})
assert_workspace_fits_visual_viewport(page)
expect(page.locator("[data-composer-shell]")).to_have_attribute("data-keyboard-open", "true")
assert_compact_composer_controls(page)
assert_textarea_grows_with_content(page)
assert_reply_input_is_visible(page)
expect(page.locator("[data-composer-close]")).to_be_visible()
page.locator("[data-composer-close]").click()
expect(page.locator("[data-composer-shell].is-collapsed")).to_be_visible()
assert_workspace_fits_visual_viewport(page)
conversation.expect_latest_message_above_composer()