fix payement link and gallery image
This commit is contained in:
1 parent
de3135b588
commit
1cbbbbd0dc
24 files changed
+654
-116
No files matched your search
@@ -0,0 +1,101 @@
|
||||
from playwright.sync_api import expect
|
||||
from django.urls import reverse
|
||||
|
||||
from .base import BasePage
|
||||
|
||||
|
||||
class MessageReadsPage(BasePage):
|
||||
def acknowledgement(self, read_path, message):
|
||||
return self.page.expect_response(lambda response: response.request.method == 'POST'
|
||||
and response.url.endswith(read_path)
|
||||
and str(message.public_id) in (response.request.post_data_json or {}).get('message_ids', []))
|
||||
|
||||
def open_and_acknowledge(self, path, read_path, message):
|
||||
with self.acknowledgement(read_path, message):
|
||||
self.goto(path)
|
||||
expect(self.message(message)).to_be_in_viewport()
|
||||
|
||||
def message(self, message):
|
||||
return self.page.locator(f'[data-message-id="{message.public_id}"]')
|
||||
|
||||
def stop_initial_follow(self):
|
||||
self.page.locator('[data-conversation]').hover()
|
||||
self.page.mouse.wheel(0, -1)
|
||||
|
||||
def scroll_and_acknowledge(self, message, read_path):
|
||||
self.stop_initial_follow()
|
||||
with self.acknowledgement(read_path, message):
|
||||
self.message(message).scroll_into_view_if_needed()
|
||||
expect(self.message(message)).to_be_in_viewport()
|
||||
|
||||
def expect_unseen(self, message):
|
||||
expect(self.message(message).locator('.message-receipts')).to_have_count(0)
|
||||
|
||||
def expect_seen_by(self, message, name):
|
||||
self.stop_initial_follow()
|
||||
self.message(message).locator('.message-receipts summary').click()
|
||||
expect(self.message(message).locator('.message-receipts')).to_contain_text(name)
|
||||
|
||||
def emulate_background_tab(self):
|
||||
# Headless Chromium can keep multiple pages visible; control this signal
|
||||
# explicitly to test the same visibilitychange branch deterministically.
|
||||
self.page.add_init_script("Object.defineProperty(document, 'visibilityState', {get: () => window.receiptVisibility || 'hidden'});")
|
||||
|
||||
def activate_and_acknowledge(self, message, read_path):
|
||||
with self.acknowledgement(read_path, message):
|
||||
self.page.evaluate("window.receiptVisibility = 'visible'; document.dispatchEvent(new Event('visibilitychange'));")
|
||||
|
||||
def open_in_background(self, path, poll_path):
|
||||
with self.page.expect_response(lambda response: response.url.split('?')[0].endswith(poll_path)):
|
||||
self.goto(path)
|
||||
|
||||
def fail_first_acknowledgement(self, read_path):
|
||||
def fail(route):
|
||||
route.fulfill(status=503, content_type='application/json', body='{}')
|
||||
self.page.route('**' + read_path, fail, times=1)
|
||||
|
||||
def wait_for_successful_acknowledgement(self, read_path):
|
||||
with self.page.expect_response(lambda response: response.url.endswith(read_path) and response.status == 200):
|
||||
pass
|
||||
|
||||
|
||||
class MessageReadsRacePage(MessageReadsPage):
|
||||
def hold_poll_after_receiving_its_body(self):
|
||||
self.page.add_init_script("""
|
||||
const nativeFetch = window.fetch.bind(window);
|
||||
window.fetch = async (input, options) => {
|
||||
const url = new URL(input, location.href);
|
||||
const polling = url.pathname.includes('/messages/poll/');
|
||||
if (polling) { url.searchParams.delete('version'); url.searchParams.delete('cursor'); }
|
||||
const response = await nativeFetch(polling ? url : input, options);
|
||||
if (polling && response.ok && !window.blockedPoll) {
|
||||
window.blockedPoll = true;
|
||||
const json = response.json.bind(response);
|
||||
response.json = async () => {
|
||||
const data = await json();
|
||||
await new Promise(resolve => { window.releaseHeldPoll = resolve; });
|
||||
window.heldPollParsed = true;
|
||||
return data;
|
||||
};
|
||||
}
|
||||
return response;
|
||||
};
|
||||
""")
|
||||
|
||||
def revoke_ticket(self, ticket, recipient, session_cookie, csrf):
|
||||
self.page.context.request.post(self.url(reverse('staff_tickets:revoke', args=[ticket.public_id, recipient.public_id])),
|
||||
headers={'Cookie': session_cookie, 'X-CSRFToken': csrf}, max_redirects=0)
|
||||
|
||||
def scroll_to_revoked_message(self, message, read_path):
|
||||
self.stop_initial_follow()
|
||||
with self.page.expect_response(lambda response: response.url.endswith(read_path) and response.status == 403):
|
||||
self.message(message).scroll_into_view_if_needed()
|
||||
expect(self.page.get_by_text('دسترسی به این مکاتبات لغو شده است.', exact=True)).to_be_visible()
|
||||
|
||||
def release_poll_and_expect_no_messages(self):
|
||||
self.page.wait_for_function('typeof window.releaseHeldPoll === "function"')
|
||||
self.page.evaluate('window.releaseHeldPoll()')
|
||||
self.page.wait_for_function('window.heldPollParsed === true')
|
||||
self.page.evaluate('() => new Promise(requestAnimationFrame)')
|
||||
expect(self.page.locator('[data-message-id]')).to_have_count(0)
|
||||
expect(self.page.get_by_text('دسترسی به این مکاتبات لغو شده است.', exact=True)).to_be_visible()
|
||||
@@ -35,6 +35,10 @@ class PaymentLinkPage(PaymentsPage):
|
||||
self.page.locator('#id_paid_amount').fill('۱۲۳۴۵')
|
||||
expect(self.page.locator('#id_paid_amount')).to_have_value('۱۲٬۳۴۵')
|
||||
self.page.get_by_role('button', name='امروز', exact=True).click()
|
||||
self.page.locator('#id_receipt_image').set_input_files({'name': 'payment.png', 'mimeType': 'image/png', 'buffer': png})
|
||||
with self.page.expect_file_chooser() as picker:
|
||||
self.page.locator('label.customer-file-button').click()
|
||||
picker.value.set_files({'name': 'screenshot.png', 'mimeType': 'image/png', 'buffer': png})
|
||||
expect(self.page.locator('#receipt-file-status')).to_contain_text('screenshot.png')
|
||||
expect(self.page.locator('#receipt-file-preview')).to_be_visible()
|
||||
self.page.get_by_role('button', name='ثبت و ارسال فیش', exact=True).click()
|
||||
expect(self.page.locator('.flash')).to_contain_text('فیش ثبت شد و در انتظار بررسی است.')
|
||||
@@ -0,0 +1,108 @@
|
||||
import pytest
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
|
||||
from apps.customers.models import CustomerProfile
|
||||
from apps.messaging.models import Message
|
||||
from apps.tickets.sharing import share_staff_ticket
|
||||
from tests.test_staff_sharing import make_sharing_case
|
||||
from tests.e2e.pages.auth import AuthenticationPage
|
||||
from tests.e2e.pages.message_reads import MessageReadsPage, MessageReadsRacePage
|
||||
|
||||
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
|
||||
PASSWORD = 'SharingTest123!'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def reading_case(settings):
|
||||
settings.PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
|
||||
case = make_sharing_case()
|
||||
case['customer'].set_password(PASSWORD)
|
||||
case['customer'].save(update_fields=['password'])
|
||||
CustomerProfile.objects.create(user=case['customer'], first_name='مشتری', last_name='خواننده')
|
||||
share_staff_ticket(case['ticket'], case['sender'], case['recipient'], 'VIEW_ONLY')
|
||||
case['probes'] = [Message.objects.create(ticket=case['ticket'], author=case['sender'],
|
||||
body=f'پیام آزمایشی {i}\n' + ('متن برای آزمایش محدوده مشاهده. ' * 45)) for i in range(12)]
|
||||
return case
|
||||
|
||||
|
||||
@pytest.mark.parametrize('staff,width', [(True, 1280), (False, 390)])
|
||||
def test_only_on_screen_messages_receive_seen_and_scrolling_acknowledges_more(live_server, browser_page, reading_case, staff, width):
|
||||
c = reading_case
|
||||
namespace = 'staff_tickets' if staff else 'tickets'
|
||||
detail = reverse(namespace + ':detail', args=[c['ticket'].public_id])
|
||||
read = reverse(namespace + (':read_shares' if staff else ':read'), args=[c['ticket'].public_id])
|
||||
reader_name = 'کارشناس recipient' if staff else 'مشتری خواننده'
|
||||
oldest, latest = c['probes'][0], c['probes'][-1]
|
||||
with browser_page() as page:
|
||||
page.set_viewport_size({'width': width, 'height': 900})
|
||||
auth = AuthenticationPage(page, live_server.url)
|
||||
if staff:
|
||||
auth.login_staff(c['recipient'], PASSWORD)
|
||||
else:
|
||||
auth.login_customer(c['customer'], PASSWORD)
|
||||
MessageReadsPage(page, live_server.url).open_and_acknowledge(detail, read, latest)
|
||||
with browser_page() as page:
|
||||
AuthenticationPage(page, live_server.url).login_staff(c['sender'], PASSWORD)
|
||||
view = MessageReadsPage(page, live_server.url)
|
||||
view.goto(reverse('staff_tickets:detail', args=[c['ticket'].public_id]))
|
||||
view.expect_unseen(oldest)
|
||||
view.expect_seen_by(latest, reader_name)
|
||||
with browser_page() as page:
|
||||
auth = AuthenticationPage(page, live_server.url)
|
||||
if staff:
|
||||
auth.login_staff(c['recipient'], PASSWORD)
|
||||
else:
|
||||
auth.login_customer(c['customer'], PASSWORD)
|
||||
view = MessageReadsPage(page, live_server.url)
|
||||
view.open_and_acknowledge(detail, read, latest)
|
||||
view.scroll_and_acknowledge(oldest, read)
|
||||
with browser_page() as page:
|
||||
AuthenticationPage(page, live_server.url).login_staff(c['sender'], PASSWORD)
|
||||
view = MessageReadsPage(page, live_server.url)
|
||||
view.goto(reverse('staff_tickets:detail', args=[c['ticket'].public_id]))
|
||||
view.expect_seen_by(oldest, reader_name)
|
||||
|
||||
|
||||
def test_background_poll_does_not_mark_seen_and_failed_acknowledgement_retries(live_server, browser_page, reading_case):
|
||||
c = reading_case
|
||||
detail = reverse('staff_tickets:detail', args=[c['ticket'].public_id])
|
||||
read = reverse('staff_tickets:read_shares', args=[c['ticket'].public_id])
|
||||
poll = reverse('staff_tickets:poll', args=[c['ticket'].public_id])
|
||||
latest = c['probes'][-1]
|
||||
with browser_page() as page:
|
||||
view = MessageReadsPage(page, live_server.url)
|
||||
view.emulate_background_tab()
|
||||
AuthenticationPage(page, live_server.url).login_staff(c['recipient'], PASSWORD)
|
||||
view.open_in_background(detail, poll)
|
||||
# A second server render exposes any erroneous background receipts.
|
||||
view.open_in_background(detail, poll)
|
||||
view.expect_unseen(latest)
|
||||
view.fail_first_acknowledgement(read)
|
||||
view.activate_and_acknowledge(latest, read)
|
||||
# The first response is deliberately 503; the successful retry must arrive.
|
||||
view.wait_for_successful_acknowledgement(read)
|
||||
with browser_page() as page:
|
||||
AuthenticationPage(page, live_server.url).login_staff(c['sender'], PASSWORD)
|
||||
view = MessageReadsPage(page, live_server.url)
|
||||
view.goto(detail)
|
||||
view.expect_seen_by(latest, 'کارشناس recipient')
|
||||
|
||||
|
||||
def test_revocation_wins_over_a_poll_response_already_in_flight(live_server, browser_page, reading_case):
|
||||
c = reading_case
|
||||
detail = reverse('staff_tickets:detail', args=[c['ticket'].public_id])
|
||||
read = reverse('staff_tickets:read_shares', args=[c['ticket'].public_id])
|
||||
sender = Client()
|
||||
sender.force_login(c['sender'])
|
||||
sender.get(detail)
|
||||
csrf = sender.cookies['csrftoken'].value
|
||||
cookie = f"sessionid={sender.cookies['sessionid'].value}; csrftoken={csrf}"
|
||||
with browser_page() as reader:
|
||||
view = MessageReadsRacePage(reader, live_server.url)
|
||||
view.hold_poll_after_receiving_its_body()
|
||||
AuthenticationPage(reader, live_server.url).login_staff(c['recipient'], PASSWORD)
|
||||
view.open_and_acknowledge(detail, read, c['probes'][-1])
|
||||
view.revoke_ticket(c['ticket'], c['recipient'], cookie, csrf)
|
||||
view.scroll_to_revoked_message(c['probes'][0], read)
|
||||
view.release_poll_and_expect_no_messages()
|
||||
Reference in new issue
Block a user