import re from playwright.sync_api import expect from tests.e2e.pages.base import BasePage class TicketConversationPage(BasePage): guidance_text = "لطفاً همه توضیحات و سؤال‌های خود را در یک پیام کامل بنویسید و سپس ارسال کنید." def request_immediate_announcement(self): card = self.page.get_by_role("region", name="اطلاع‌رسانی تلفنی فوری") expect(card).to_be_visible() expect(card).to_contain_text("پیام صوتی از پیش ضبط‌شده") button = card.get_by_role("button", name="اطلاع‌رسانی تلفنی فوری") expect(button).to_be_enabled() with self.page.expect_navigation(wait_until="domcontentloaded"): button.click() expect(self.page.get_by_text("درخواست تماس توسط سرویس پذیرفته شد.")).to_be_visible() expect(self.page.get_by_role("region", name="وضعیت اطلاع‌رسانی‌های تلفنی")).to_contain_text("درخواست فوری") def send_reply(self, body): self.page.locator("[data-composer-toggle]").click() reply = self.page.locator('form[data-chat-coach] textarea[name="body"]') expect(reply).to_be_visible() reply.fill(body) with self.page.expect_navigation(wait_until="domcontentloaded"): self.page.locator('form[data-chat-coach] [data-primary-submit]').click() assert "reply_result=reply" in self.page.url expect(self.page.locator("[data-conversation]")).to_contain_text(body) def open_composer(self): launch = self.page.locator("[data-composer-toggle]") expect(launch).to_be_visible() launch.click() expect(self.page.locator("[data-composer-shell]:not(.is-collapsed)")).to_be_visible() def expect_latest_message_above_composer(self): latest = self.page.locator("[data-message]").last expect(latest).to_be_visible() latest.scroll_into_view_if_needed() message_box = latest.bounding_box() composer_box = self.page.locator("[data-composer-shell]").bounding_box() assert message_box and composer_box assert message_box["y"] + message_box["height"] <= composer_box["y"] + 1 @property def chat_guidance(self): return self.page.locator(".chat-coach-floating") def expect_guidance_visible(self): expect(self.chat_guidance).to_be_visible() expect(self.chat_guidance).to_contain_text(self.guidance_text) def expect_guidance_hidden(self): expect(self.chat_guidance).to_be_hidden() def dismiss_guidance(self): self.chat_guidance.get_by_role("button", name="بستن راهنما").click() self.expect_guidance_hidden() class PublicTicketCreatePage(BasePage): def open(self): self.goto("/tickets/new/") expect(self.page.locator('[data-wizard-step="1"]')).to_be_visible() expect(self.page.locator('[data-ticket-subject]')).to_be_visible() @property def unit(self): return self.page.locator("[data-ticket-unit]") @property def expert_picker(self): return self.page.locator("[data-expert-picker]") @property def category(self): return self.page.locator("[data-ticket-category]") @property def queue(self): return self.page.locator("[data-general-queue]") @property def file_preview(self): return self.page.locator("[data-file-preview]") def select_unit(self, unit_id): unit_id = str(unit_id) with self.page.expect_response( lambda response: "/tickets/new/options/" in response.url and f"unit={unit_id}" in response.url and response.status == 200 ) as response_info: self.unit.evaluate( "(node, value) => { node.value = value; node.dispatchEvent(new Event('change', {bubbles: true})); }", unit_id, ) payload = response_info.value.json() expect(self.page.locator("[data-general-queue]")).to_have_attribute("data-unit-id", unit_id) expected_ids = {str(item["id"]) for item in payload["experts"]} actual_ids = set(self.expert_picker.locator('input[name="selected_expert"]:not([value=""])').evaluate_all("nodes => nodes.map(node => node.value)")) assert actual_ids == expected_ids return payload def select_search_result(self, query, result_text): self.page.locator("[data-ticket-search]").fill(query) result = self.page.locator("[data-ticket-search-results] button").filter(has_text=result_text).first expect(result).to_be_visible() result.click() def select_expert(self, display_name): card = self.expert_picker.locator("label.expert-card").filter(has_text=display_name) expect(card).to_have_count(1) radio = card.locator('input[name="selected_expert"]') radio.evaluate( "node => { node.checked = true; node.dispatchEvent(new Event('change', {bubbles: true})); }" ) expect(radio).to_be_checked() return radio.get_attribute("value") def select_general_queue(self): radio = self.queue.locator('input[name="selected_expert"]') radio.evaluate( "node => { node.checked = true; node.dispatchEvent(new Event('change', {bubbles: true})); }" ) expect(radio).to_be_checked() def select_category(self, category_id): self.category.evaluate( "(node, value) => { node.value = value; node.dispatchEvent(new Event('change', {bubbles: true})); }", str(category_id), ) expect(self.category).to_have_value(str(category_id)) def upload_gallery(self, files, *, total_count=None): self.page.locator("[data-gallery-input]").set_input_files(files) expect(self.file_preview.locator(".file-chip")).to_have_count(total_count or len(files)) def upload_documents(self, files, *, total_count=None): self.page.locator("[data-document-input]").set_input_files(files) expect(self.file_preview.locator(".file-chip")).to_have_count(total_count or len(files)) def remove_attachment(self, filename): chip = self.file_preview.locator(".file-chip").filter(has_text=filename) expect(chip).to_have_count(1) chip.locator("button").evaluate("node => node.click()") expect(self.file_preview.locator(".file-chip").filter(has_text=filename)).to_have_count(0) def submit(self, *, subject, body): self.page.locator('input[name="subject"]').fill(subject) self.page.locator('[data-wizard-step="1"] [data-wizard-next]').click() expect(self.page.locator('[data-wizard-step="2"]')).to_be_visible() self.page.locator('[data-wizard-step="2"] [data-wizard-next]').click() expect(self.page.locator('[data-wizard-step="3"]')).to_be_visible() self.page.locator('[data-wizard-step="3"] [data-wizard-next]').click() expect(self.page.locator('[data-wizard-step="4"]')).to_be_visible() self.page.locator('textarea[name="body"]').fill(body) self.page.locator('[data-wizard-step="4"] [data-wizard-next]').click() expect(self.page.locator('[data-wizard-step="5"]')).to_be_visible() expect(self.page.locator('[data-review-subject]')).to_have_text(subject) with self.page.expect_navigation(wait_until="domcontentloaded"): self.page.get_by_role("button", name="ثبت و ارسال نهایی").click() expect(self.page).to_have_url(re.compile(r"/tickets/(?!new/)[^/]+/$")) expect(self.page.locator("h1")).to_have_text(subject)