From 96da0feaa8c3f86745e41015750ddbfe07cef1b1 Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Fri, 3 Jan 2025 20:00:52 +0000 Subject: [PATCH 01/11] WIP Fix #1431 As shop owner I can pause all payment collections --- .gitignore | 2 + subscribie/__init__.py | 8 +- subscribie/blueprints/admin/__init__.py | 140 ++++++++++++++---- .../admin/templates/admin/dashboard.html | 10 ++ .../admin/subscriber/show_subscriber.html | 20 ++- .../templates/admin/subscribers-archived.html | 2 +- .../admin/templates/admin/subscribers.html | 48 +++++- .../subscribers_bulk_operations_index.html | 56 +++++++ .../templates/admin/upcoming_invoices.html | 2 +- subscribie/notifications.py | 6 +- subscribie/receivers.py | 6 +- subscribie/signals.py | 3 +- subscribie/utils.py | 40 ++--- 13 files changed, 268 insertions(+), 75 deletions(-) create mode 100644 subscribie/blueprints/admin/templates/admin/subscribers_bulk_operations_index.html diff --git a/.gitignore b/.gitignore index d74c81dfb..4ee7ef7c4 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ tests/browser-automated-tests-playwright/index.spec.js-snapshots/* tests/browser-automated-tests-playwright/worker* tests/browser-automated-tests-playwright/e2e/*-snapshots tests/browser-automated-tests-playwright/test-videos/* +tests/browser-automated-tests-playwright/graphviz_output* subscribie/static/* subscribie/custom_pages/* playwright-report @@ -54,3 +55,4 @@ playwright-report emails *.bk email-queue +uploads/* diff --git a/subscribie/__init__.py b/subscribie/__init__.py index e2b3cb57b..2b7ea41f5 100644 --- a/subscribie/__init__.py +++ b/subscribie/__init__.py @@ -2,7 +2,7 @@ """ subscribie.app ~~~~~~~~~ - A microframework for buiding subsciption websites. + A microframework for building Subscription websites. This module implements the central subscribie application. :copyright: (c) 2018 by Karma Computing Ltd @@ -216,9 +216,9 @@ def start_session(): # Note that PriceLists must also be assigned to plan(s) to be in effect. price_lists = PriceList.query.all() # If there are zero PriceLists this may mean shop is outdated and - # therefore needs its inital PriceLists created + # therefore needs its initial PriceLists created if len(price_lists) == 0: - # Create defaul PriceList with zero rules for each suported currency + # Create default PriceList with zero rules for each supported currency for currency in settings.get("SUPPORTED_CURRENCIES"): log.debug( f"Creating PriceList with zero rules for currency {currency}" # noqa: E501 @@ -386,7 +386,7 @@ def alert_subscriber_update_choices(subscriber: Person): ) alert_subscriber_update_choices(person) - @app.route("/test-lanuage") + @app.route("/test-language") def test_language(): return _("Hello") diff --git a/subscribie/blueprints/admin/__init__.py b/subscribie/blueprints/admin/__init__.py index 3c398717b..9bfd18f26 100644 --- a/subscribie/blueprints/admin/__init__.py +++ b/subscribie/blueprints/admin/__init__.py @@ -2,6 +2,7 @@ import json from subscribie import settings from subscribie.database import database # noqa +from subscribie.tasks import background_task from flask import ( Blueprint, render_template, @@ -249,7 +250,7 @@ def update_payment_fulfillment(stripe_external_id): @admin.route("/stripe/charge", methods=["POST", "GET"]) @login_required def stripe_create_charge(): - """Charge an existing subscriber x ammount immediately + """Charge an existing subscriber x amount immediately :param stripe_customer_id: Stripe customer id :param amount: Positive integer amount to charge in smallest currency unit @@ -267,7 +268,7 @@ def stripe_create_charge(): currency = data["currency"] statement_descriptor_suffix = data["statement_descriptor_suffix"] except Exception: - # Assumme form submission + # Assume form submission # Get stripe customer_id from subscribers subscription -> customer reference person = Person.query.get(request.form.get("person_id")) stripe_subscription_id = person.subscriptions[0].stripe_subscription_id @@ -334,12 +335,84 @@ def stripe_create_charge(): return jsonify(paymentIntent.status) +@background_task +def do_pause_stripe_subscription_payment_collection( + subscription_id, pause_collection_behavior="keep_as_draft", app=None +): + """Pause pause_stripe_subscription payment collections via its + Stripe subscription_id + + Choices of `pause_collection_behavior` include: + + - keep_as_draft (Subscribie default- Temporarily offer services for free, and + collect payment later) + - void (Temporarily offer services for free and never collect payment) + - mark_uncollectible (Temporarily offer services for free and + mark invoice as uncollectible) + See also: https://docs.stripe.com/billing/subscriptions/pause-payment + """ + with app.app_context(): + stripe.api_key = get_stripe_secret_key() + connect_account_id = get_stripe_connect_account_id() + + if subscription_id is None: + log.error("subscription_id cannot be None") + return False + try: + stripe_subscription = stripe.Subscription.retrieve( + subscription_id, stripe_account=connect_account_id + ) + if stripe_subscription.status != "canceled": + stripe_pause = stripe.Subscription.modify( + subscription_id, + stripe_account=connect_account_id, + pause_collection={"behavior": pause_collection_behavior}, + ) + # filtering for the pause_collection value + stripe_pause_filter = stripe_pause["pause_collection"]["behavior"] + + # adding the pause_collection status to the + # stripe_pause_collection column + pause_collection = Subscription.query.filter_by( + stripe_subscription_id=subscription_id + ).first() + + pause_collection.stripe_pause_collection = stripe_pause_filter + database.session.commit() + log.debug(f"Subscription paused ({subscription_id})") + else: + log.debug( + f"Skipping. Subscription {subscription_id} because it's canceled." + ) + except Exception as e: + msg = f"Error pausing subscription ({subscription_id})" + log.error(f"{msg}. {e}") + raise + + +@background_task +def do_pause_all_stripe_subscriptions(app=None): + # For each Subscription object, get it's Stripe subscription + # object and pause it using pause_stripe_subscription. + with app.app_context(): + subscriptions = Subscription.query.all() + for subscription in subscriptions: + log.debug(f"Attempting to pause subscription {subscription.uuid}") + stripe_subscription_id = subscription.stripe_subscription_id + try: + do_pause_stripe_subscription_payment_collection( + stripe_subscription_id, app=current_app + ) + except Exception as e: + log.error( + f"Error trying to pause subscription {subscription.uuid}. Error: {e}" # noqa: E501 + ) + + @admin.route("/stripe/subscriptions//actions/pause") @login_required def pause_stripe_subscription(subscription_id: str): """Pause a Stripe subscription""" - stripe.api_key = get_stripe_secret_key() - connect_account_id = get_stripe_connect_account_id() if "confirm" in request.args and request.args["confirm"] != "1": return render_template( @@ -349,31 +422,24 @@ def pause_stripe_subscription(subscription_id: str): ) if "confirm" in request.args and request.args["confirm"] == "1": try: - stripe_pause = stripe.Subscription.modify( - subscription_id, - stripe_account=connect_account_id, - pause_collection={"behavior": "void"}, + do_pause_stripe_subscription_payment_collection( + subscription_id, pause_collection_behavior="void", app=current_app ) - # filtering for the pause_collection value - stripe_pause_filter = stripe_pause["pause_collection"]["behavior"] - - # adding the pause_collection status to the stripe_pause_collection column - pause_collection = Subscription.query.filter_by( - stripe_subscription_id=subscription_id - ).first() - - pause_collection.stripe_pause_collection = stripe_pause_filter - database.session.commit() - flash("Subscription paused") - except Exception as e: - msg = "Error pausing subscription" + except Exception: + msg = f"Error pausing subscription ({subscription_id})" flash(msg) - log.error(f"{msg}. {e}") - return redirect(url_for("admin.subscribers")) +@admin.route("/stripe/subscriptions/all/actions/pause") +@login_required +def pause_all_subscribers_subscriptions(): + """Bulk action to pause all subscriptions in the shop""" + do_pause_all_stripe_subscriptions() # Background task + return """All payment collections are being paused in the background. You can move away from this page.""" # noqa: E501 + + @admin.route("/stripe/subscriptions//actions/resume") @login_required def resume_stripe_subscription(subscription_id): @@ -525,7 +591,7 @@ def edit(): Note plans are immutable, when a change is made to plan, its old plan is archived and a new plan is created with a new uuid. This is to - protect data integriry and make sure plan history is retained, via its uuid. + protect data integrity and make sure plan history is retained, via its uuid. If a user needs to change a subscription, they should change to a different plan with a different uuid. @@ -1030,7 +1096,7 @@ def stripe_connect(): log.error(e) account = None - # Setup Stripe webhook endpoint if it dosent already exist + # Setup Stripe webhook endpoint if it doesn't already exist if account: # Attempt to Updates an existing Account Capability to accept card payments try: @@ -1411,11 +1477,23 @@ def subscribers(): ) +@admin.route("/subscribers/bulk-operations") +@login_required +def subscribers_bulk_operations_index(): + + num_active_subscribers = get_number_of_active_subscribers() + return render_template( + "admin/subscribers_bulk_operations_index.html", + num_active_subscribers=num_active_subscribers, + confirm=request.args.get("confirm"), + ) + + @admin.route("/recent-subscription-cancellations") @login_required def show_recent_subscription_cancellations(): """Get the last 30 days subscription cancellations (if any) - Note: Stripe api only guarentees the last 30 days of events. + Note: Stripe api only guarantees the last 30 days of events. At time of writing this method performs no caching of events, see StripeInvoice for possible improvements """ @@ -1439,7 +1517,7 @@ def show_recent_subscription_cancellations(): ) if person is None: log.info( - f"""Person query retruned None- probably archived.\n + f"""Person query returned None- probably archived.\n Skipping Person with uuid {value.data.object.metadata.person_uuid}""" ) continue @@ -1700,7 +1778,7 @@ def add_shop_admin(): form = AddShopAdminForm() if request.method == "POST": if form.validate_on_submit(): - # Check user dosent already exist + # Check user doesn't already exist email = escape(request.form["email"].lower()) if User.query.filter_by(email=email).first() is not None: return f"Error, admin with email ({email}) already exists." @@ -1747,7 +1825,7 @@ def delete_admin_confirmation(id: int): else: User.query.filter_by(id=id).delete() database.session.commit() - flash("Account was deleted succesfully") + flash("Account was deleted successfully") except Exception as e: msg = "Error deleting the admin account" @@ -1926,7 +2004,7 @@ def rename_shop_post(): @admin.route("/announce-stripe-connect", methods=["GET"]) def announce_shop_stripe_connect_ids(): - """Accounce this shop's stripe connect account id(s) + """Announce this shop's stripe connect account id(s) to the STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST - stripe_live_connect_account_id - stripe_test_connect_account_id @@ -2162,7 +2240,7 @@ def enable_geo_currency(): @admin.route("/spamcheck/") @login_required def check_spam(account_name) -> int: - """Check if shop name is likley to be spam or not""" + """Check if shop name is likely to be spam or not""" from subscribie.anti_spam_subscribie_shop_names.run import detect_spam_shop_name return str(detect_spam_shop_name(account_name)) diff --git a/subscribie/blueprints/admin/templates/admin/dashboard.html b/subscribie/blueprints/admin/templates/admin/dashboard.html index 326a34133..17375100d 100644 --- a/subscribie/blueprints/admin/templates/admin/dashboard.html +++ b/subscribie/blueprints/admin/templates/admin/dashboard.html @@ -113,6 +113,16 @@

Stats

Export Subscribers +
+

+ Perform bulk actions across all subscribers such as + pause all Subscribers payment collections. +

+ + Pause all Subscribers payment collections + +
diff --git a/subscribie/blueprints/admin/templates/admin/subscriber/show_subscriber.html b/subscribie/blueprints/admin/templates/admin/subscriber/show_subscriber.html index f4c147b0d..426f3efe6 100644 --- a/subscribie/blueprints/admin/templates/admin/subscriber/show_subscriber.html +++ b/subscribie/blueprints/admin/templates/admin/subscriber/show_subscriber.html @@ -136,7 +136,7 @@

Why might this Subscriber owe money?

The card had insufficient funds to complete the purchase at the time it was charged

{% elif decline_code == "generic_decline" %}
What does "{{ decline_code }}" mean? -

The card was declined for an unknown reason or incorrectly flaged as a risky payment.

+

The card was declined for an unknown reason or incorrectly flagged as a risky payment.

The customer needs to contact their card issuer for more information.

{% endif %} @@ -208,7 +208,19 @@

Subscriptions

{% for subscription in person.subscriptions %}
  • {{ subscription.plan.title }}
    - Status: {{ subscription.stripe_status or 'Unknown' }} {% if subscription.transactions|length > 0 %}(Refresh){% endif %} + Subscription Status: {{ subscription.stripe_status or 'Unknown' }} {% if subscription.transactions|length > 0 %}(Refresh){% endif %} +
    + Payment Collection Status: + {% if subscription.plan.requirements and subscription.plan.requirements.subscription %} + {% if subscription.stripe_pause_collection == "keep_as_draft" %} + Paused - (keep_as_draft) +
    + explain +

    Temporarily offer services for free and have the possibility of collecting payment later.

    +

    Invoices are still generated, however no payment collection attempts are made against them. +

    + {% endif %} + {% endif %}
    Started: {{ subscription.created_at.strftime('%d-%m-%Y') }}
    Interval Unit: @@ -258,7 +270,7 @@

    Subscriptions

    {% else %}
      {% for document in subscription.documents %} - {# Show documents assocated with subscription (if any) #} + {# Show documents associated with subscription (if any) #}
    • {{ document.name }} | {{ document.created_at.strftime('%Y-%m-%d') }}
    • @@ -300,7 +312,7 @@

      Invoices

      padding: 8px; } #subscriber-invoices tr { - border-bottom: 1px solid lightgray; + border-bottom: 1px solid lightgrey; } #subscriber-invoices td { text-align: center; diff --git a/subscribie/blueprints/admin/templates/admin/subscribers-archived.html b/subscribie/blueprints/admin/templates/admin/subscribers-archived.html index 0aaa84568..2da0dbd09 100644 --- a/subscribie/blueprints/admin/templates/admin/subscribers-archived.html +++ b/subscribie/blueprints/admin/templates/admin/subscribers-archived.html @@ -90,7 +90,7 @@

      Archived Subscribers

      (No up-front fee) {% endif %} -
    • Status: +
    • Subscription Status: {% if subscription.plan.requirements and subscription.plan.requirements.subscription %} {{ subscription.stripe_status }} {% else %} diff --git a/subscribie/blueprints/admin/templates/admin/subscribers.html b/subscribie/blueprints/admin/templates/admin/subscribers.html index f5516e21b..e090c1618 100644 --- a/subscribie/blueprints/admin/templates/admin/subscribers.html +++ b/subscribie/blueprints/admin/templates/admin/subscribers.html @@ -142,10 +142,46 @@

      Search...

      (No up-front fee) {% endif %} -
    • Status: +
    • Subscription Status: + {% if subscription.plan.requirements and subscription.plan.requirements.subscription %} + {% if subscription.stripe_pause_collection == "void" %} +
      Paused - void future invoices
      + {% elif subscription.stripe_status == "canceled" %} +
      Canceled
      + {% elif subscription.stripe_status == 'active' %} +
      Active
      + {% elif subscription.stripe_status == 'past_due' %} +
      past_due
      + {% else %} +
      {{ subscription.stripe_status }}
      + {% endif %} + {% else %} +
      Paid
      + {% endif %} +
    • +
    • Payment Collection: {% if subscription.plan.requirements and subscription.plan.requirements.subscription %} - {% if subscription.stripe_pause_collection == "void" %} -
      Paused
      + {% if subscription.stripe_pause_collection == "keep_as_draft" %} +
      Paused - (keep_as_draft) +
      + explain +
      +

      Temporarily offer services for free and have the possibility of collecting payment later.

      +

      Invoices are still generated, however no payment collection attempts are made against them. +

      +
      +
      + {% elif subscription.stripe_pause_collection == "void" %} +
      Paused - void future invoices +
      + explain +
      +

      Temporarily offer services for free and never collect payment.

      +

      Invoices are still generated, however they are marked as void (also known as 'cancelled invoice'), + so you'll still have an invoice as a record

      +
      +
      +
      {% elif subscription.stripe_status == "canceled" %}
      Canceled
      {% elif subscription.stripe_status == 'active' %} @@ -155,7 +191,6 @@

      Search...

      {% else %}
      {{ subscription.stripe_status }}
      {% endif %} - {# always show Force Refresh link so long as there's > 0 transactions against a subscription #} {% else %}
      Paid
      {% endif %} @@ -184,6 +219,7 @@

      Search...

      {% endif %}
    • Actions:
      + {# always show Force Refresh link so long as there's > 0 transactions against a subscription #} {% if subscription.transactions|length > 0 %} Refresh Status @@ -228,7 +264,7 @@

      Search...

      {% else %}
      {% elif person.transactions %} {# Donations are not in the subscription table since they are not plans nor subscriptions #} - {# therefore we need to look at the transction table to find the donations #} + {# therefore we need to look at the transaction table to find the donations #} {% for transaction in person.transactions %} {% if transaction.is_donation %}
      diff --git a/subscribie/blueprints/admin/templates/admin/subscribers_bulk_operations_index.html b/subscribie/blueprints/admin/templates/admin/subscribers_bulk_operations_index.html new file mode 100644 index 000000000..34e34a02e --- /dev/null +++ b/subscribie/blueprints/admin/templates/admin/subscribers_bulk_operations_index.html @@ -0,0 +1,56 @@ +{% extends "admin/layout.html" %} +{% block title %} {{ title }} {% endblock %} + +{% block body %} + +

      Subscribers Bulk Operations

      + +
      + +
      + + +
      +
      +
      + {% if 'confirm' not in request.args %} +
      +

      These bulk operations effect all Subscribers in your shop. This can save you time, for example, + if you want to pause payment collection from every subscriber in your shop.

      +
      +
      +

      Pause payment collection for every Subscription

      +

      When you pause payment collection for every Subscription in your shop, no payment collections will be + attempted for the Subscriptions. Invoices will still be generated so you have a record, but no + payment collection attempts will be made. +

      + +
      + {% else %} +
      +

      Are you sure you want to pause payment collection for all Subscriptions for every Subscriber?

      +

      Once paused, payment collection won't take place. Invoices will still be generated (so you have a record) + but payments won't be collected. +

      + +
      + {% endif %} +
      +
      +
      +{% endblock body %} diff --git a/subscribie/blueprints/admin/templates/admin/upcoming_invoices.html b/subscribie/blueprints/admin/templates/admin/upcoming_invoices.html index 2bcd5597f..210ff9439 100644 --- a/subscribie/blueprints/admin/templates/admin/upcoming_invoices.html +++ b/subscribie/blueprints/admin/templates/admin/upcoming_invoices.html @@ -94,7 +94,7 @@

      Upcoming Invoices

      (No up-front cost) {% endif %}
    • -
    • Status: +
    • Subscription Status: {% if upcomingInvoice.subscription.plan.requirements.subscription %} {{ upcomingInvoice.subscription.stripe_status }} {% else %} diff --git a/subscribie/notifications.py b/subscribie/notifications.py index c48c6dc29..a0423169d 100644 --- a/subscribie/notifications.py +++ b/subscribie/notifications.py @@ -26,8 +26,8 @@ def newSubscriberEmailNotification(*args, **kwargs): ) msg["subject"] = f"{company.name} - new subscriber ({subscriber_email})" msg["from"] = current_app.config["EMAIL_LOGIN_FROM"] - shopadmins = User.query.all() # all shop admins - msg["to"] = [user.email for user in shopadmins] # all shop admins + shop_admins = User.query.all() # all shop admins + msg["to"] = [user.email for user in shop_admins] # all shop admins # use user-new-subscriber-notification.jinja2.html email_template = str( Path( @@ -50,7 +50,7 @@ def newSubscriberEmailNotification(*args, **kwargs): log.info("queueing new subscriber notification email") msg.queue() except Exception as e: - log.error(f"failed to send newsubscriberemailnotification email: {e}") + log.error(f"failed to send newSubscriberEmailNotification email: {e}") @background_task diff --git a/subscribie/receivers.py b/subscribie/receivers.py index 5d1ef3e15..ffa981a0c 100644 --- a/subscribie/receivers.py +++ b/subscribie/receivers.py @@ -71,7 +71,7 @@ def receiver_attach_documents_to_subscription(*args, **kwargs): @background_task def receiver_send_subscriber_payment_failed_notification_email(*args, **kwargs): - """Recieve stripe payment_intent.payment_failed signal""" + """Receive stripe payment_intent.payment_failed signal""" log.debug("In receiver_send_subscriber_payment_failed_notification_email") if "stripe_event" not in kwargs: log.error( @@ -133,11 +133,11 @@ def receiver_new_subscriber(*args, **kwargs): def receiver_new_subscriber_send_to_mailchimp(*args, **kwargs) -> None: """ - Send new subscriber contact information to Mainchimp. + Send new subscriber contact information to Mailchimp. For this to work, Subscribie needs to have: - Mailchimp api key - - Mainchimp Audience id (maichimp lets you add contacts + - Mailchimp Audience id (Mailchimp lets you add contacts to *audiences* (aka lists), not simply one large pot of contacts- which is good. - It is the responsibility of the Shop owner to create the diff --git a/subscribie/signals.py b/subscribie/signals.py index 0936c22dd..7439694f4 100644 --- a/subscribie/signals.py +++ b/subscribie/signals.py @@ -15,7 +15,6 @@ and a Subscriber wants to know, and a logging system etc) """ from blinker import signal -from subscribie.notifications import newSubscriberEmailNotification from subscribie.receivers import ( receiver_send_subscriber_payment_failed_notification_email, receiver_new_subscriber, @@ -44,7 +43,7 @@ def register_signal_handlers(): - """Connect signals to recievers + """Connect signals to receivers This is called during flask app startup in views.py """ signal_new_subscriber.connect(receiver_new_subscriber) diff --git a/subscribie/utils.py b/subscribie/utils.py index 8ac662747..390108361 100644 --- a/subscribie/utils.py +++ b/subscribie/utils.py @@ -1,6 +1,6 @@ -from flask import current_app, request, g, session, url_for +from flask import request, g, session, url_for import stripe -from subscribie import database +from subscribie import settings, database from currency_symbols import CurrencySymbols import logging from subscribie.tasks import background_task @@ -144,9 +144,9 @@ def get_stripe_secret_key(): payment_provider = PaymentProvider.query.first() if payment_provider.stripe_livemode: - return current_app.config.get("STRIPE_LIVE_SECRET_KEY", None) + return settings.get("STRIPE_LIVE_SECRET_KEY", None) else: - return current_app.config.get("STRIPE_TEST_SECRET_KEY", None) + return settings.get("STRIPE_TEST_SECRET_KEY", None) def get_stripe_publishable_key(): @@ -154,9 +154,9 @@ def get_stripe_publishable_key(): payment_provider = PaymentProvider.query.first() if payment_provider.stripe_livemode: - return current_app.config.get("STRIPE_LIVE_PUBLISHABLE_KEY", None) + return settings.get("STRIPE_LIVE_PUBLISHABLE_KEY", None) else: - return current_app.config.get("STRIPE_TEST_PUBLISHABLE_KEY", None) + return settings.get("STRIPE_TEST_PUBLISHABLE_KEY", None) def create_stripe_connect_account(company, country_code=None, default_currency=None): @@ -319,7 +319,7 @@ def announce_stripe_connect_account(account_id, live_mode=0): log.debug(f"Announcing stripe account to {url_for('index', _external=True)}") from subscribie.models import PaymentProvider # noqa: F401 - ANNOUNCE_HOST = current_app.config["STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST"] + ANNOUNCE_HOST = settings.get("STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST") req = requests.post( ANNOUNCE_HOST, json={ @@ -332,8 +332,8 @@ def announce_stripe_connect_account(account_id, live_mode=0): msg = { "msg": f"Announced Stripe connect account {account_id} \ for site_url {request.host_url}, to the STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST: \ -{current_app.config['STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST']}\n\ -WARNING: Check logs to verify recipt" +{settings.get('STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST')}\n\ +WARNING: Check logs to verify receipt" } log.debug(msg) req.raise_for_status() @@ -476,7 +476,7 @@ def get_stripe_invoices(app, last_n_days=30): def stripe_invoice_failed(stripeInvoice): - """Returns true/false if a Stripe Invoice has failed all collection attemts + """Returns true/false if a Stripe Invoice has failed all collection attempts and no further *automated* collection will take place.""" if stripeInvoice.subscribie_subscription_id: if ( @@ -507,7 +507,7 @@ def stripe_invoice_failing(stripeInvoice): def getBadInvoices(): - """Return both failed and failing invocies + """Return both failed and failing invoices What's a bad invoice? @@ -524,7 +524,7 @@ def getBadInvoices(): def get_stripe_failing_subscription_invoices(): """Return list of stripe failing invoices Note: remember that failing invoices may still - get automatic attemps to be collected + get automatic attempts to be collected """ failingInvoices = [] from subscribie.models import StripeInvoice @@ -620,7 +620,7 @@ def dec2pence(amount: str) -> int: def backfill_transactions(days=30): """Backfill transaction data in an idempotent way Useful for fixing webhook delivery misses (such as if all webhook delivery retires - exausted), and data corrections from Hotfixes. + exhausted), and data corrections from Hotfixes. - .e.g created_at See https://github.com/Subscribie/subscribie/issues/1385 """ @@ -670,7 +670,7 @@ def backfill_transactions(days=30): def backfill_subscriptions(days=30): """Backfill subscription data in an idempotent way Useful for fixing webhook delivery misses (such as if all webhook delivery retires - exausted), and data corrections from Hotfixes. + exhausted), and data corrections from Hotfixes. - .e.g created_at See https://github.com/Subscribie/subscribie/issues/1385 """ @@ -722,7 +722,7 @@ def backfill_subscriptions(days=30): def backfill_persons(days=30): """Backfill person data in an idempotent way Useful for fixing webhook delivery misses (such as if all webhook delivery retires - exausted), and data corrections from Hotfixes. + exhausted), and data corrections from Hotfixes. NOTE: The Stripe session checkout object is used here to signify the earliest known date/time for Person.created_at time @@ -757,7 +757,7 @@ def backfill_persons(days=30): for stripe_session in stripe_checkout_sessions.auto_paging_iter(): if stripe_session.metadata.get("subscribie_checkout_session_id") is None: log.warning( - f"No subscribie_checkout_session_id found on metadata for stripe_session {stripe_session.id}" + f"No subscribie_checkout_session_id found on metadata for stripe_session {stripe_session.id}" # noqa: E501 ) # noqa: E501 continue @@ -773,7 +773,7 @@ def backfill_persons(days=30): if subscribie_subscription is not None: if subscribie_subscription.person is None: log.warning( - f"Skipping stripe_session {stripe_session.id} as person is None for subscription {subscribie_subscription.id}" + f"Skipping stripe_session {stripe_session.id} as person is None for subscription {subscribie_subscription.id}" # noqa: E501 ) # noqa: E501 continue # Update the subscribie_subscription in Subscription model @@ -786,7 +786,7 @@ def backfill_persons(days=30): stripe_session_created_at = datetime.fromtimestamp( stripe_session.created ) # noqa: E501 - msg = f"Infering person create_at from stripe_session_created_at: {stripe_session_created_at}" # noqa: E501 + msg = f"Inferring person create_at from stripe_session_created_at: {stripe_session_created_at}" # noqa: E501 log.info(msg) msg = f"Setting person.created_at to {stripe_session_created_at}" # noqa: E501 log.info(msg) @@ -797,7 +797,7 @@ def backfill_persons(days=30): def backfill_stripe_invoices(days=30): """Backfill stripe_invoice data in an idempotent way Useful for fixing webhook delivery misses (such as if all webhook delivery retires - exausted), and data corrections from Hotfixes. + exhausted), and data corrections from Hotfixes. - .e.g created_at See https://github.com/Subscribie/subscribie/issues/1385 @@ -824,7 +824,7 @@ def backfill_stripe_invoices(days=30): .first() ) if local_stripe_invoice is not None: - # Update the local_stripe_invoice in DtripeInvoice model + # Update the local_stripe_invoice in StripeInvoice model log.debug(f"At local_stripe_invoice.id: {local_stripe_invoice.id}") msg = f"Current local_stripe_invoice.created_at: {local_stripe_invoice.created_at}" # noqa: E501 log.info(msg) From cc4d04d5f1a79e53ffecf6e213e8f972e8bea2ba Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Fri, 3 Jan 2025 23:02:36 +0000 Subject: [PATCH 02/11] #1431 set SERVER_NAME during pr deploy --- .envsubst.template | 86 ---------------------------- .github/workflows/pr-demo-deploy.yml | 5 +- 2 files changed, 3 insertions(+), 88 deletions(-) delete mode 100644 .envsubst.template diff --git a/.envsubst.template b/.envsubst.template deleted file mode 100644 index 2aa409bb5..000000000 --- a/.envsubst.template +++ /dev/null @@ -1,86 +0,0 @@ -FLASK_ENV=${FLASK_ENV} - -# Software as a service (SAAS) -SAAS_URL=${SAAS_URL} -# SAAS_API_KEY is to allow subscribie platform to send authenticated -# api requests to subscribie shops created by the shop builder. -SAAS_API_KEY=${SAAS_API_KEY} -SAAS_ACTIVATE_ACCOUNT_PATH=${SAAS_ACTIVATE_ACCOUNT_PATH} - -# For testing this repo in isolation, SUBSCRIBIE_REPO_DIRECTORY can be './' -# for production, SUBSCRIBIE_REPO_DIRECTORY should be wherever the repo -# is cloned to -SUBSCRIBIE_REPO_DIRECTORY=${SUBSCRIBIE_REPO_DIRECTORY} -SQLALCHEMY_TRACK_MODIFICATIONS=False -SQLALCHEMY_DATABASE_URI=${SQLALCHEMY_DATABASE_URI} -SECRET_KEY="random string. e.g. echo -e 'from os import urandom\\nprint urandom(25)' | python" -DB_FULL_PATH=${DB_FULL_PATH} -MODULES_PATH="./modules/" -TEMPLATE_BASE_DIR=${TEMPLATE_BASE_DIR} -THEME_NAME="jesmond" -CUSTOM_PAGES_PATH=${CUSTOM_PAGES_PATH} -UPLOADED_IMAGES_DEST=${UPLOADED_IMAGES_DEST} -UPLOADED_FILES_DEST=${UPLOADED_FILES_DEST} -# Default 50Mb upload limit -MAX_CONTENT_LENGTH="52428800" -SUCCESS_REDIRECT_URL=${SUCCESS_REDIRECT_URL} -THANKYOU_URL=${THANKYOU_URL} -EMAIL_LOGIN_FROM=${EMAIL_LOGIN_FROM} -EMAIL_QUEUE_FOLDER=${EMAIL_QUEUE_FOLDER} - - -SERVER_NAME=${SERVER_NAME} - -# Cookie policies -#SESSION_COOKIE_SECURE=True -#SESSION_COOKIE_HTTPONLY=True -#SESSION_COOKIE_SAMESITE=None - -PERMANENT_SESSION_LIFETIME=${PERMANENT_SESSION_LIFETIME} - -MAIL_DEFAULT_SENDER=${MAIL_DEFAULT_SENDER} - -# Supported currencies -SUPPORTED_CURRENCIES=${SUPPORTED_CURRENCIES} - -STRIPE_LIVE_PUBLISHABLE_KEY=${STRIPE_LIVE_PUBLISHABLE_KEY} -STRIPE_LIVE_SECRET_KEY=${STRIPE_LIVE_SECRET_KEY} - -STRIPE_TEST_PUBLISHABLE_KEY=${STRIPE_TEST_PUBLISHABLE_KEY} -STRIPE_TEST_SECRET_KEY=${STRIPE_TEST_SECRET_KEY} - -# Internal server where shop should send its stripe connect account id to. See https://github.com/Subscribie/subscribie/issues/352 -STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST=${STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST} - - -# For development: - -# Python log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL -# See https://docs.python.org/3/howto/logging.html -PYTHON_LOG_LEVEL=DEBUG - -# Playwright testing -PLAYWRIGHT_HOST=http://127.0.0.1:5000/ -PLAYWRIGHT_HEADLESS=true - -#rename shop variables -PATH_TO_SITES=${PATH_TO_SITES} -PATH_TO_RENAME_SCRIPT=${PATH_TO_RENAME_SCRIPT} -SUBSCRIBIE_DOMAIN="subscriby.shop" - -PRIVATE_KEY="/tmp/private.pem" -PUBLIC_KEY="/tmp/public.pem" - -ANTI_SPAM_SHOP_NAMES_MODEL_FULL_PATH=/changeme - - -# Optional -TELEGRAM_TOKEN= -TELEGRAM_CHAT_ID= -TELEGRAM_PYTHON_LOG_LEVEL="ERROR" - - -# Environment Settings for tests -TEST_SHOP_OWNER_EMAIL_ISSUE_704=admin@example.com -TEST_SHOP_OWNER_LOGIN_URL=http://127.0.0.1:5000/auth/login - diff --git a/.github/workflows/pr-demo-deploy.yml b/.github/workflows/pr-demo-deploy.yml index d694107a5..3365d08e3 100644 --- a/.github/workflows/pr-demo-deploy.yml +++ b/.github/workflows/pr-demo-deploy.yml @@ -6,7 +6,7 @@ name: Deploy pr preview on: pull_request: - # (pull_request_target get fired on external contributer pull requests) + # (pull_request_target get fired on external contributor pull requests) #pull_request_target paths-ignore: - '**/README.md' @@ -46,7 +46,7 @@ jobs: # Enforce max 60 chars, always end with alnum char echo SUBDOMAIN=`echo "${{ github.head_ref }}" | tr '[:upper:]' '[:lower:]' | cut -c -60 | rev | sed 's/[^[:alnum:]]//1' | rev` >> $GITHUB_ENV echo $GITHUB_ENV - - name: Create dokku app for pr branch if dosent already exist using dokku apps:create + - name: Create dokku app for pr branch if doesn't already exist using dokku apps:create env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} DOKKU_HOST: ${{ secrets.DOKKU_HOST }} @@ -66,6 +66,7 @@ jobs: ssh dokku@$DOKKU_HOST -C "dokku config:set --no-restart ${{ env.SUBDOMAIN }} STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST=${{ secrets.STRIPE_CONNECT_ACCOUNT_ANNOUNCER_HOST }}" ssh dokku@$DOKKU_HOST -C "dokku config:set --no-restart ${{ env.SUBDOMAIN }} FLASK_ENV=development" ssh dokku@$DOKKU_HOST -C "dokku config:set --no-restart ${{ env.SUBDOMAIN }} EMAIL_QUEUE_FOLDER=${{ secrets.DOKKU_EMAIL_QUEUE_FOLDER }}" + ssh dokku@$DOKKU_HOST -C "dokku config:set --no-restart ${{ env.SUBDOMAIN }} SERVER_NAME=${{ env.SUBDOMAIN }}.pcpink.co.uk" # mount email-queue folder ssh dokku@$DOKKU_HOST -C "dokku storage:mount ${{ github.head_ref }} ${{ secrets.HOST_EMAIL_QUEUE_PATH }}:${{ secrets.DOKKU_EMAIL_QUEUE_FOLDER }} && dokku ps:restart ${{ github.head_ref }} || true" From 25c6e9242b5bfbee9122dc0ee8001a58a3c424fe Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Sat, 4 Jan 2025 13:19:35 +0000 Subject: [PATCH 03/11] #1431 Fix test 264_shop_owner_create_plan_with_choice_options --- .../e2e/133_shop_owner_plan_creation.spec.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js b/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js index a710487d2..a8e879681 100644 --- a/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js @@ -177,7 +177,7 @@ test.describe("Plan Creation tests:", () => { try { const check_plan_with_choice_and_options = await page.textContent('text="Plan with choice and options"', { timeout: 3000 }); expect(check_plan_with_choice_and_options === "Plan with choice and options"); - await page.click("text=See choice options"); + await page.click("text=See choice options", { timeout: 2_000 }); await page.click("text=Choices (2 options)"); //check if plan options are blue and red const check_plan_option_red = await page.textContent('text="Red"'); @@ -239,7 +239,6 @@ test.describe("Plan Creation tests:", () => { await page.goto('/admin/list-choice-groups'); await page.click("text=Options"); - await page.getByRole('button', { name: 'Options' }).click() await page.click("text=Add Option"); await page.fill('.form-control', 'Red'); await page.click("text='Save'"); @@ -266,8 +265,8 @@ test.describe("Plan Creation tests:", () => { //confirm choice and option plan was added await page.goto('/'); - const free_trial = await page.textContent('text="Plan with choice and options"'); - expect(free_trial === "Plan with choice and options"); + const plan_with_choice_options = await page.textContent('text="Plan with choice and options"'); + expect(plan_with_choice_options === "Plan with choice and options"); //check if plan options are blue and red const check_plan_option_red = await page.textContent('text="Red"'); @@ -284,5 +283,3 @@ test.describe("Plan Creation tests:", () => { }); }); - -//module.exports = plan_creation; From f5e1dde726301eeebabb655031c017f9ff0db649 Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Sat, 4 Jan 2025 22:09:31 +0000 Subject: [PATCH 04/11] Ref #1431 update playwright to use yaml schema settings over dotenv --- package-lock.json | 52 ++++++++++++++++++- package.json | 5 +- subscribie/settings.py | 1 + ...ber_terms_and_condition_check_test.spec.js | 4 +- .../1065_subscriber_checkout_donation.spec.js | 2 +- .../e2e/1219_custom_thank_you_url.spec.js | 2 +- ...criber_order_plan_with_cooling_off.spec.js | 9 ++-- ...h_choice_options_and_required_note.spec.js | 14 ++--- ...er_plan_with_only_recurring_charge.spec.js | 2 +- ...rder_plan_with_only_upfront_charge.spec.js | 2 +- .../playwright.config.ts | 21 +++++++- 11 files changed, 88 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 420815484..d778f1103 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "1.0.0", "license": "GPL-3.0-or-later", "dependencies": { - "dotenv": "^8.2.0" + "dotenv": "^8.2.0", + "yaml": "^2.7.0" }, "devDependencies": { - "@playwright/test": "^1.44.1" + "@playwright/test": "^1.44.1", + "@types/node": "^22.10.5" } }, "node_modules/@playwright/test": { @@ -30,6 +32,15 @@ "node": ">=16" } }, + "node_modules/@types/node": { + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", @@ -81,6 +92,23 @@ "engines": { "node": ">=16" } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } } }, "dependencies": { @@ -93,6 +121,15 @@ "playwright": "1.44.1" } }, + "@types/node": { + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", + "dev": true, + "requires": { + "undici-types": "~6.20.0" + } + }, "dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", @@ -120,6 +157,17 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", "dev": true + }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, + "yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==" } } } diff --git a/package.json b/package.json index 047e5e986..351b81d58 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,11 @@ "test": "tests" }, "dependencies": { - "dotenv": "^8.2.0" + "yaml": "^2.7.0" }, "devDependencies": { - "@playwright/test": "^1.44.1" + "@playwright/test": "^1.44.1", + "@types/node": "^22.10.5" }, "scripts": { "test": "node tests/browser-automated-tests-playwright/index.js" diff --git a/subscribie/settings.py b/subscribie/settings.py index 512bf788a..5575d9b10 100644 --- a/subscribie/settings.py +++ b/subscribie/settings.py @@ -51,6 +51,7 @@ "TELEGRAM_CHAT_ID": Str(), "TELEGRAM_PYTHON_LOG_LEVEL": Str(), "TEST_SHOP_OWNER_EMAIL_ISSUE_704": Email(), + "TEST_SUBSCRIBER_EMAIL_USER": Email(), "TEST_SHOP_OWNER_LOGIN_URL": Url(), } ) diff --git a/tests/browser-automated-tests-playwright/e2e/1005_subscriber_terms_and_condition_check_test.spec.js b/tests/browser-automated-tests-playwright/e2e/1005_subscriber_terms_and_condition_check_test.spec.js index a77427574..178466fc6 100644 --- a/tests/browser-automated-tests-playwright/e2e/1005_subscriber_terms_and_condition_check_test.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/1005_subscriber_terms_and_condition_check_test.spec.js @@ -1,6 +1,6 @@ const { test, expect } = require('@playwright/test'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const TEST_SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test('@1005@subscriber @1005_subscriber_terms_and_condition_check_test', async ({ page }) => { @@ -10,7 +10,7 @@ test('@1005@subscriber @1005_subscriber_terms_and_condition_check_test', async ( await page.goto("/account/logout"); //login in as subscriber await page.goto("/account/login"); - await page.fill('#email', SUBSCRIBER_EMAIL_USER); + await page.fill('#email', TEST_SUBSCRIBER_EMAIL_USER); await page.fill('#password', 'password'); await page.click('text=Sign In'); await page.textContent('.card-title') === "Your subscriptions"; diff --git a/tests/browser-automated-tests-playwright/e2e/1065_subscriber_checkout_donation.spec.js b/tests/browser-automated-tests-playwright/e2e/1065_subscriber_checkout_donation.spec.js index 6d711618c..9f5327620 100644 --- a/tests/browser-automated-tests-playwright/e2e/1065_subscriber_checkout_donation.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/1065_subscriber_checkout_donation.spec.js @@ -1,7 +1,7 @@ const { test, expect } = require('@playwright/test'); const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("checkout a donation:", () => { test("@1065 @1065_subscriber_checkout_donation", async ({ page }) => { diff --git a/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js b/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js index c258a0a19..29dffd559 100644 --- a/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js @@ -1,5 +1,5 @@ const { test, expect } = require('@playwright/test'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; const { admin_login } = require('./features/admin_login'); test.describe("order free plan tests:", () => { test("@1219 @shop-owner @1219_custom_thank_you_url @1219_shop-owner_enable_custom_url", async ({ page }) => { diff --git a/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js b/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js index 9e33a8560..fe5b739bd 100644 --- a/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js @@ -3,7 +3,7 @@ const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); const { fetch_upcomming_invoices } = require('./features/fetch_upcomming_invoices'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const TEST_SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with cooling off:", () => { test("@133 @133_subscriber_order_plan_with_cooling_off_period", async ({ page }) => { console.log("Ordering plan with cooling off"); @@ -17,7 +17,7 @@ test.describe("order plan with cooling off:", () => { // Fill in order form await page.fill('#given_name', 'John'); await page.fill('#family_name', 'Smith'); - await page.fill('#email', SUBSCRIBER_EMAIL_USER); + await page.fill('#email', TEST_SUBSCRIBER_EMAIL_USER); await page.fill('#mobile', '07123456789'); await page.fill('#address_line_one', '123 Short Road'); await page.fill('#city', 'London'); @@ -49,11 +49,10 @@ test.describe("order plan with cooling off:", () => { // Verify get to the thank you page order complete const order_complete_content = await page.textContent('.title-1'); expect(order_complete_content === "Order Complete!"); - // expect(await page.screenshot()).toMatchSnapshot('cooling-off-order-complete.png'); // Go to My Subscribers page // Crude wait before we check subscribers to allow webhooks time - await new Promise(x => setTimeout(x, 5000)); //5 secconds + await new Promise(x => setTimeout(x, 5000)); //5 seconds await page.goto('/admin/subscribers') // expect(await page.screenshot()).toMatchSnapshot('cooling-off-view-subscribers.png'); @@ -69,7 +68,7 @@ test.describe("order plan with cooling off:", () => { // Verify that subscriber is present in the list const subscriber_email_content = await page.textContent('.subscriber-email'); - expect(subscriber_email_content === SUBSCRIBER_EMAIL_USER); + expect(subscriber_email_content === TEST_SUBSCRIBER_EMAIL_USER); // Verify that plan is attached to subscriber const subscriber_plan_title_content = await page.textContent('.subscription-title'); diff --git a/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js b/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js index a252fb88b..4eff77c69 100644 --- a/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js @@ -3,7 +3,7 @@ const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); const { fetch_upcomming_invoices } = require('./features/fetch_upcomming_invoices'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test("@264@subscriber @264_subscriber_order_plan_with_choice_options_and_required_note", async ({ page }) => { await admin_login(page); await set_test_name_cookie(page, "@264_subscriber_order_plan_with_choice_options_and_required_note") @@ -23,7 +23,7 @@ test("@264@subscriber @264_subscriber_order_plan_with_choice_options_and_require await page.fill('#given_name', 'John'); await page.fill('#family_name', 'Smith'); await page.fill('#email', SUBSCRIBER_EMAIL_USER); - await page.fill('#mobile', '07123456789'); + await page. fill('#mobile', '07123456789'); await page.fill('#address_line_one', '123 Short Road'); await page.fill('#city', 'London'); await page.fill('#postcode', 'L01 T3U'); @@ -76,7 +76,7 @@ test("@264@subscriber @264_subscriber_order_plan_with_choice_options_and_require const subscriber_plan_title_content = await page.textContent('.subscription-title'); expect(subscriber_plan_title_content === 'Plan with choice and options'); - const content_subscriber_plan_interval_amount = await page.textContent('.subscribers-plan-interval_amount'); + const content_subscriber_plan_interval_amount = await page.locator('css=span.plan-price-interval').filter({ hasTest: '£15' }); expect(content_subscriber_plan_interval_amount === '£15.00'); const subscriber_plan_sell_price_content = await page.evaluate(() => document.querySelector('.subscribers-plan-sell-price').textContent.indexOf("(No up-front fee)")); @@ -85,17 +85,13 @@ test("@264@subscriber @264_subscriber_order_plan_with_choice_options_and_require const subscriber_plan_choice_content = await page.textContent('text="Red"'); expect(subscriber_plan_choice_content === "Red"); - const subscriber_plan_note_content = await page.textContent('text="Purple"'); - expect(subscriber_plan_note_content === "Purple"); + await page.textContent('text="Purple"'); // Go to upcoming payments and ensure plan is attached to upcoming invoice await page.goto('/admin/upcoming-invoices'); // Fetch Upcoming Invoices await fetch_upcomming_invoices(page); - await page.click('#fetch_upcoming_invoices'); - await new Promise(x => setTimeout(x, 10000)); //10 secconds - const content_upcoming_invoice_plan_price_interval = await page.textContent('.plan-price-interval'); - expect(content_upcoming_invoice_plan_price_interval === '£15.00'); + await page.locator('css=span.plan-price-interval', {hasText: "£15.00"}).first().textContent() === '£15.00'; const content_upcoming_invoice_plan_sell_price = await page.textContent('.upcoming-invoices-plan-no-sell_price'); expect(content_upcoming_invoice_plan_sell_price === '(No up-front cost)'); diff --git a/tests/browser-automated-tests-playwright/e2e/293-1_subscriber_order_plan_with_only_recurring_charge.spec.js b/tests/browser-automated-tests-playwright/e2e/293-1_subscriber_order_plan_with_only_recurring_charge.spec.js index 2bf712c6a..85a2fb6bc 100644 --- a/tests/browser-automated-tests-playwright/e2e/293-1_subscriber_order_plan_with_only_recurring_charge.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/293-1_subscriber_order_plan_with_only_recurring_charge.spec.js @@ -3,7 +3,7 @@ const { admin_login } = require('./features/admin_login'); const { fetch_upcomming_invoices } = require('./features/fetch_upcomming_invoices'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with only recurring charge test:", () => { test("@293-1@subscriber@Ordering recurring plan @293_shop_owner_order_recurring_plan", async ({ page }) => { await admin_login(page); diff --git a/tests/browser-automated-tests-playwright/e2e/293-2_subscriber_order_plan_with_only_upfront_charge.spec.js b/tests/browser-automated-tests-playwright/e2e/293-2_subscriber_order_plan_with_only_upfront_charge.spec.js index 45e3cb6c3..7edfba92f 100644 --- a/tests/browser-automated-tests-playwright/e2e/293-2_subscriber_order_plan_with_only_upfront_charge.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/293-2_subscriber_order_plan_with_only_upfront_charge.spec.js @@ -1,7 +1,7 @@ const { test, expect } = require('@playwright/test'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); const { admin_login } = require('./features/admin_login'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with only upfront charge tests:", () => { diff --git a/tests/browser-automated-tests-playwright/playwright.config.ts b/tests/browser-automated-tests-playwright/playwright.config.ts index 688647113..46422c03c 100644 --- a/tests/browser-automated-tests-playwright/playwright.config.ts +++ b/tests/browser-automated-tests-playwright/playwright.config.ts @@ -1,9 +1,22 @@ import { defineConfig, devices } from '@playwright/test'; +import YAML from 'yaml'; +import fs from 'fs'; +const path = require('path'); /** - * Read environment variables from file. + * Read the settings.yml configuration file. */ -require('dotenv').config() + +const settings_file = fs.readFileSync(path.resolve(__dirname, '../../settings.yaml'), 'utf8'); + +// Take each element from the YAML object and populate the process.env object. +const settings = YAML.parse(settings_file); +for (const key in settings) { + console.log(`Setting ${key} to ${settings[key]} in process.env`); + process.env[key] = settings[key]; +} + + const PLAYWRIGHT_HEADLESS = process.env.PLAYWRIGHT_HEADLESS.toLocaleLowerCase() == "true" || false; const PLAYWRIGHT_HOST = process.env.PLAYWRIGHT_HOST; const PLAYWRIGHT_SLOWMO = parseInt(process.env.PLAYWRIGHT_SLOWMO); @@ -49,5 +62,9 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, ], }); From 9c76f7000a3e1dba0dc298cdb444ddbc2b003b80 Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Sun, 5 Jan 2025 13:08:08 +0000 Subject: [PATCH 05/11] CodeQL action version bumps --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 344d3cb20..f3b885d5c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,11 +39,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,4 +52,4 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 From 87bcbb606c2a481bafed0d48abe592af7925c30e Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Sun, 5 Jan 2025 13:11:48 +0000 Subject: [PATCH 06/11] #431 bump checkout actions to v4 --- .github/workflows/demo-videos.yml | 2 +- .github/workflows/git-auto-issue-branch-creation.yml | 2 +- .github/workflows/pr-demo-deploy.yml | 2 +- .github/workflows/prod-test-onboarding-works.yml | 2 +- .github/workflows/removing-inactive-pr-preview.yml | 2 +- .github/workflows/test-email-shopowner-704.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/demo-videos.yml b/.github/workflows/demo-videos.yml index 740b5c0c1..f01eeddc6 100644 --- a/.github/workflows/demo-videos.yml +++ b/.github/workflows/demo-videos.yml @@ -61,7 +61,7 @@ jobs: ssh dokku@$DOKKU_HOST -C "dokku config:set --no-restart ${{ env.SUBDOMAIN }} EMAIL_QUEUE_FOLDER=${{ secrets.DOKKU_EMAIL_QUEUE_FOLDER }}" # mount email-queue folder ssh dokku@$DOKKU_HOST -C "dokku storage:mount ${{ env.SUBDOMAIN}} ${{ secrets.HOST_EMAIL_QUEUE_PATH }}:${{ secrets.DOKKU_EMAIL_QUEUE_FOLDER }} && dokku ps:restart ${{ env.SUBDOMAIN }} || true" - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Deploy branch ${{ github.event.repository.default_branch }} to dokku diff --git a/.github/workflows/git-auto-issue-branch-creation.yml b/.github/workflows/git-auto-issue-branch-creation.yml index 6ef76eb90..529dc9482 100644 --- a/.github/workflows/git-auto-issue-branch-creation.yml +++ b/.github/workflows/git-auto-issue-branch-creation.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Get issue number and title id: issue diff --git a/.github/workflows/pr-demo-deploy.yml b/.github/workflows/pr-demo-deploy.yml index 3365d08e3..554a99246 100644 --- a/.github/workflows/pr-demo-deploy.yml +++ b/.github/workflows/pr-demo-deploy.yml @@ -26,7 +26,7 @@ jobs: GITHUB_CONTEXT: ${{ toJSON(github) }} run: | echo $GITHUB_CONTEXT - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: psf/black@stable # Fail early if fails Black code style diff --git a/.github/workflows/prod-test-onboarding-works.yml b/.github/workflows/prod-test-onboarding-works.yml index 88be1e00d..3b02ea5ab 100644 --- a/.github/workflows/prod-test-onboarding-works.yml +++ b/.github/workflows/prod-test-onboarding-works.yml @@ -11,7 +11,7 @@ jobs: timeout-minutes: 15 environment: production steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v1 diff --git a/.github/workflows/removing-inactive-pr-preview.yml b/.github/workflows/removing-inactive-pr-preview.yml index 66a50bfff..c3302701c 100644 --- a/.github/workflows/removing-inactive-pr-preview.yml +++ b/.github/workflows/removing-inactive-pr-preview.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v2 diff --git a/.github/workflows/test-email-shopowner-704.yml b/.github/workflows/test-email-shopowner-704.yml index 0071d4c9e..85b84ef8d 100644 --- a/.github/workflows/test-email-shopowner-704.yml +++ b/.github/workflows/test-email-shopowner-704.yml @@ -12,7 +12,7 @@ jobs: timeout-minutes: 60 environment: production steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 From 1a73d83e6a04dae8a912333bc61e2775bf81ef7d Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Sun, 5 Jan 2025 13:30:02 +0000 Subject: [PATCH 07/11] #1431 +TEST_SUBSCRIBER_EMAIL_USER to settings.yaml.example --- settings.yaml.example | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.yaml.example b/settings.yaml.example index 735f44519..8b7243a97 100644 --- a/settings.yaml.example +++ b/settings.yaml.example @@ -82,5 +82,6 @@ TELEGRAM_PYTHON_LOG_LEVEL: ERROR # Environment Settings for tests TEST_SHOP_OWNER_EMAIL_ISSUE_704: admin@example.com +TEST_SUBSCRIBER_EMAIL_USER: test@example.co.uk TEST_SHOP_OWNER_LOGIN_URL: http://127.0.0.1:5000/auth/login From fa0065fa29473b63da7265bf4027714a54ef66c0 Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Sun, 5 Jan 2025 13:38:24 +0000 Subject: [PATCH 08/11] #1431 SUBSCRIBER_EMAIL_USER -> TEST_SUBSCRIBER_EMAIL_USER --- ...147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js | 2 +- ...scriber_order_plan_with_recurring_and_upfront_charge.spec.js | 2 +- .../e2e/293_subscriber_order_plan_with_only_recurring_charge.js | 2 +- .../e2e/293_subscriber_order_plan_with_only_upfront_charge.js | 2 +- ...3_subscriber_order_plan_with_recurring_and_upfront_charge.js | 2 +- .../e2e/463_subscriber_ordering_plan_with_VAT.spec.js | 2 +- .../e2e/475_subscriber_order_plan_with_free_trial.spec.js | 2 +- .../e2e/516_subscriber_order_plan_with_cancel_at.spec.js | 2 +- .../e2e/623_subscriber_magic_login.spec.js | 2 +- .../e2e/905-subscriber-search-by-email-and-name.spec.js | 2 +- ...subscriber_order_free_plan_with_terms_and_conditions.spec.js | 2 +- .../e2e/993_subscriber_change_card_details.spec.js | 2 +- .../e2e/checkSubscriberLogin.js | 2 +- .../e2e/shop_owner_pause_resume_and_cancel_subscriptions.js | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js b/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js index ea5377af4..f463b960c 100644 --- a/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js @@ -1,7 +1,7 @@ const { test, expect } = require('@playwright/test'); const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; //Subscribie tests test.describe("Pause, Resume and Cancel Subscription:", () => { test("@147@shop_owner @147_shop_owner_pause_resume_and_cancel_subscriptions", async ({ page }) => { diff --git a/tests/browser-automated-tests-playwright/e2e/293-3_subscriber_order_plan_with_recurring_and_upfront_charge.spec.js b/tests/browser-automated-tests-playwright/e2e/293-3_subscriber_order_plan_with_recurring_and_upfront_charge.spec.js index 206579ac9..b5c767de1 100644 --- a/tests/browser-automated-tests-playwright/e2e/293-3_subscriber_order_plan_with_recurring_and_upfront_charge.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/293-3_subscriber_order_plan_with_recurring_and_upfront_charge.spec.js @@ -3,7 +3,7 @@ const { set_test_name_cookie } = require('./features/set_test_name_cookie'); const { admin_login } = require('./features/admin_login'); const { fetch_upcomming_invoices } = require('./features/fetch_upcomming_invoices'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with recurring and upfront charge test:", () => { test("@293-3 @293-3_subscriber_order_plan_with_recurring_and_upfront_charge", async ({ page }) => { console.log("@293-3_subscriber_order_plan_with_recurring_and_upfront_charge"); diff --git a/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_recurring_charge.js b/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_recurring_charge.js index bfd89867f..0cb86815c 100644 --- a/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_recurring_charge.js +++ b/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_recurring_charge.js @@ -1,5 +1,5 @@ const { test, expect } = require('@playwright/test'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with only recurring charge test:", () => { test("@293@subscriber@Ordering recurring plan", async ({ page }) => { console.log("Ordering plan with only recurring charge..."); diff --git a/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_upfront_charge.js b/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_upfront_charge.js index efceb8a1e..e98c54f90 100644 --- a/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_upfront_charge.js +++ b/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_only_upfront_charge.js @@ -1,5 +1,5 @@ const { test, expect } = require('@playwright/test'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with only upfront charge tests:", () => { test("@293@subscriber@Ordering upfront plan", async ({ page }) => { diff --git a/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_recurring_and_upfront_charge.js b/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_recurring_and_upfront_charge.js index 7c98acda7..4bc2eaec3 100644 --- a/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_recurring_and_upfront_charge.js +++ b/tests/browser-automated-tests-playwright/e2e/293_subscriber_order_plan_with_recurring_and_upfront_charge.js @@ -1,5 +1,5 @@ const { test, expect } = require('@playwright/test'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with recurring and upfront charge test:", () => { test("@293@subscriber@Ordering recurring and upfront plan", async ({ page }) => { console.log("Ordering Plan with subscription and upfront charge"); diff --git a/tests/browser-automated-tests-playwright/e2e/463_subscriber_ordering_plan_with_VAT.spec.js b/tests/browser-automated-tests-playwright/e2e/463_subscriber_ordering_plan_with_VAT.spec.js index fcad538a4..044f68458 100644 --- a/tests/browser-automated-tests-playwright/e2e/463_subscriber_ordering_plan_with_VAT.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/463_subscriber_ordering_plan_with_VAT.spec.js @@ -3,7 +3,7 @@ const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); const { fetch_upcomming_invoices } = require('./features/fetch_upcomming_invoices'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test("@463@subscriber@Ordering plan with VAT @463_subscriber_order_plan_with_vat", async ({ page }) => { await admin_login(page); await set_test_name_cookie(page, "@463_order_plan_with_vat") diff --git a/tests/browser-automated-tests-playwright/e2e/475_subscriber_order_plan_with_free_trial.spec.js b/tests/browser-automated-tests-playwright/e2e/475_subscriber_order_plan_with_free_trial.spec.js index 127f5e2b9..c1bb176e9 100644 --- a/tests/browser-automated-tests-playwright/e2e/475_subscriber_order_plan_with_free_trial.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/475_subscriber_order_plan_with_free_trial.spec.js @@ -3,7 +3,7 @@ const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); const { fetch_upcomming_invoices } = require('./features/fetch_upcomming_invoices'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with free-trial:", () => { test("@475@subscriber @475_subscriber_order_plan_with_free_trial", async ({ page }) => { await admin_login(page); diff --git a/tests/browser-automated-tests-playwright/e2e/516_subscriber_order_plan_with_cancel_at.spec.js b/tests/browser-automated-tests-playwright/e2e/516_subscriber_order_plan_with_cancel_at.spec.js index 3d1400873..bc1469ded 100644 --- a/tests/browser-automated-tests-playwright/e2e/516_subscriber_order_plan_with_cancel_at.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/516_subscriber_order_plan_with_cancel_at.spec.js @@ -3,7 +3,7 @@ const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); const { fetch_upcomming_invoices } = require('./features/fetch_upcomming_invoices') -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order plan with cancel at:", () => { test("@516 @516_shop_owner_order_plan_with_cancel_at", async ({ page }) => { await admin_login(page); diff --git a/tests/browser-automated-tests-playwright/e2e/623_subscriber_magic_login.spec.js b/tests/browser-automated-tests-playwright/e2e/623_subscriber_magic_login.spec.js index 5da652dac..227d6ddf9 100644 --- a/tests/browser-automated-tests-playwright/e2e/623_subscriber_magic_login.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/623_subscriber_magic_login.spec.js @@ -1,6 +1,6 @@ const { test, expect } = require('@playwright/test'); const checkSubscriberLogin = require('./checkSubscriberLogin.js'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test('@623@subscriber@ @623_subscriber_magic_login_and_reset_password', async ({ page }) => { diff --git a/tests/browser-automated-tests-playwright/e2e/905-subscriber-search-by-email-and-name.spec.js b/tests/browser-automated-tests-playwright/e2e/905-subscriber-search-by-email-and-name.spec.js index 64fe9b7ec..d11bce13f 100644 --- a/tests/browser-automated-tests-playwright/e2e/905-subscriber-search-by-email-and-name.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/905-subscriber-search-by-email-and-name.spec.js @@ -1,7 +1,7 @@ const { test, expect } = require('@playwright/test'); const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test("@905 @905-subscriber-search-by-email-and-name", async ({ page }) => { console.log("subscriber filter by name and by email"); diff --git a/tests/browser-automated-tests-playwright/e2e/939_subscriber_order_free_plan_with_terms_and_conditions.spec.js b/tests/browser-automated-tests-playwright/e2e/939_subscriber_order_free_plan_with_terms_and_conditions.spec.js index 6f3978af6..f5fa5d30e 100644 --- a/tests/browser-automated-tests-playwright/e2e/939_subscriber_order_free_plan_with_terms_and_conditions.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/939_subscriber_order_free_plan_with_terms_and_conditions.spec.js @@ -2,7 +2,7 @@ const { test, expect } = require('@playwright/test'); const { admin_login } = require('./features/admin_login'); const { set_test_name_cookie } = require('./features/set_test_name_cookie'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; test.describe("order free plan tests:", () => { test("@939 @subscriber @939_subscriber_order_free_plan_with_terms_and_conditions", async ({ page }) => { diff --git a/tests/browser-automated-tests-playwright/e2e/993_subscriber_change_card_details.spec.js b/tests/browser-automated-tests-playwright/e2e/993_subscriber_change_card_details.spec.js index dd7b7776b..a3f323d2b 100644 --- a/tests/browser-automated-tests-playwright/e2e/993_subscriber_change_card_details.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/993_subscriber_change_card_details.spec.js @@ -1,5 +1,5 @@ const { test, expect } = require('@playwright/test'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; const { set_test_name_cookie } = require('./features/set_test_name_cookie'); diff --git a/tests/browser-automated-tests-playwright/e2e/checkSubscriberLogin.js b/tests/browser-automated-tests-playwright/e2e/checkSubscriberLogin.js index 00a0ad7de..ece627aeb 100644 --- a/tests/browser-automated-tests-playwright/e2e/checkSubscriberLogin.js +++ b/tests/browser-automated-tests-playwright/e2e/checkSubscriberLogin.js @@ -3,7 +3,7 @@ const https = require('https'); function checkSubscriberLogin() { console.log("executing checkSubscribierLogin") const SUBSCRIBER_EMAIL_HOST = process.env.SUBSCRIBER_EMAIL_HOST - const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER + const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER const SUBSCRIBER_EMAIL_PASSWORD = process.env.SUBSCRIBER_EMAIL_PASSWORD const IMAP_SEARCH_UNSEEN = parseInt(process.env.IMAP_SEARCH_UNSEEN) const RESET_PASSWORD_IMAP_SEARCH_SUBJECT = process.env.RESET_PASSWORD_IMAP_SEARCH_SUBJECT diff --git a/tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js b/tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js index b3a1f355b..6ff515ed0 100644 --- a/tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js +++ b/tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js @@ -1,5 +1,5 @@ const { test, expect } = require('@playwright/test'); -const SUBSCRIBER_EMAIL_USER = process.env.SUBSCRIBER_EMAIL_USER; +const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; //Subscribie tests test.describe("Pause, Resume and Cancel Subscription:", () => { test("@147@shop-owner@Pause and Resume transaction", async ({ page }) => { From 0404783814ecae66e7e0a8dc8f3af7521e195f6a Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Sun, 5 Jan 2025 13:39:25 +0000 Subject: [PATCH 09/11] #1431 don't delete stripe account during playwright test runs --- .../e2e/1_stripe_connect.spec.js | 5 ----- .../e2e/293_shop_owner_connect_to_stripe.spec.ts | 6 ------ 2 files changed, 11 deletions(-) diff --git a/tests/browser-automated-tests-playwright/e2e/1_stripe_connect.spec.js b/tests/browser-automated-tests-playwright/e2e/1_stripe_connect.spec.js index 2e33a87e8..b0520c5f2 100644 --- a/tests/browser-automated-tests-playwright/e2e/1_stripe_connect.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/1_stripe_connect.spec.js @@ -29,11 +29,6 @@ test.describe("connecting to stripe:", () => { // Go to Stripe Connect payment gateways page await page.goto('/admin/connect/stripe-connect'); - // deleting connect account id, if stripe was not succesfully connected - await page.goto('/admin/delete-connect-account'); - await page.goto('/admin/dashboard'); - console.log('deleting connect account id'); - // Start Stripe connect onboarding await page.goto('/admin/connect/stripe-connect'); // Selecting countring to connect to stripe diff --git a/tests/browser-automated-tests-playwright/e2e/293_shop_owner_connect_to_stripe.spec.ts b/tests/browser-automated-tests-playwright/e2e/293_shop_owner_connect_to_stripe.spec.ts index 47bd23002..b714bb4a4 100644 --- a/tests/browser-automated-tests-playwright/e2e/293_shop_owner_connect_to_stripe.spec.ts +++ b/tests/browser-automated-tests-playwright/e2e/293_shop_owner_connect_to_stripe.spec.ts @@ -38,12 +38,6 @@ test.describe("Subscribie tests:", () => { await page.goto('admin/connect/stripe-connect'); let contentStripeConnect = await page.evaluate(() => document.body.textContent); test.skip(contentStripeConnect.indexOf("Your currently running in test mode.") > -1); - //expect(await page.screenshot()).toMatchSnapshot('stripe_status.png'); - - // deleting connect account id, if stripe was not succesfully connected - await page.goto('/admin/delete-connect-account'); - await page.goto('/admin/dashboard'); - console.log('deleting connect account id'); // Start Stripe connect onboarding await page.goto('/admin/connect/stripe-connect'); From afbb2ae99597b67d5953455996d488fd1556c55b Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Mon, 6 Jan 2025 01:17:43 +0000 Subject: [PATCH 10/11] #1431 remove duplicate tests & fix other tests --- .../admin/templates/admin/dashboard.html | 1 + subscribie/settings.py | 9 +- ...wner_terms_and_conditions_creation.spec.js | 1 - .../e2e/1219_custom_thank_you_url.spec.js | 1 + .../e2e/133_shop_owner_plan_creation.spec.js | 2 +- ...criber_order_plan_with_cooling_off.spec.js | 4 +- ...se_resume_and_cancel_subscriptions.spec.js | 7 +- ...r_pause_resume_and_cancel_subscriptions.js | 73 ----- .../e2e/shop_owner_plan_creation.js | 305 ------------------ ...action_filter_by_name_and_by_plan_title.js | 36 --- .../playwright.config.ts | 5 +- 11 files changed, 20 insertions(+), 424 deletions(-) delete mode 100644 tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js delete mode 100644 tests/browser-automated-tests-playwright/e2e/shop_owner_plan_creation.js delete mode 100644 tests/browser-automated-tests-playwright/e2e/shop_owner_transaction_filter_by_name_and_by_plan_title.js diff --git a/subscribie/blueprints/admin/templates/admin/dashboard.html b/subscribie/blueprints/admin/templates/admin/dashboard.html index 17375100d..566879e6d 100644 --- a/subscribie/blueprints/admin/templates/admin/dashboard.html +++ b/subscribie/blueprints/admin/templates/admin/dashboard.html @@ -30,6 +30,7 @@

      Checklist

    • {% endif %} {% if reply_to_email_address is sameas None %} +
    • 🔔 Improve support for your subscribers by setting your support email address 🔔
    • diff --git a/subscribie/settings.py b/subscribie/settings.py index 5575d9b10..3c54ab7ae 100644 --- a/subscribie/settings.py +++ b/subscribie/settings.py @@ -64,9 +64,16 @@ def load_settings(): for key in schema._required_keys: if key in os.environ: print(f"Overriding setting {key} with environ value: {os.getenv(key)}") + print( + ( + "If overriding keeps happening & you can't " + "work out why- check your IDE (hello vscode) " + "isn't getting in the way. Try using a terminal instead." + ) + ) settings[key] = os.getenv(key) return settings -# Load app setttings via strictyaml & schema +# Load app settings via strictyaml & schema settings = load_settings().data diff --git a/tests/browser-automated-tests-playwright/e2e/1005_shop_owner_terms_and_conditions_creation.spec.js b/tests/browser-automated-tests-playwright/e2e/1005_shop_owner_terms_and_conditions_creation.spec.js index 95c87498d..76900a993 100644 --- a/tests/browser-automated-tests-playwright/e2e/1005_shop_owner_terms_and_conditions_creation.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/1005_shop_owner_terms_and_conditions_creation.spec.js @@ -10,7 +10,6 @@ test("@1005@shop-owner @1005_shop_owner_terms_and_conditions_creation", async ({ console.log("checking if terms and conditions is already attached to the free plan..."); await page.goto('/'); await page.click('[name="Free plan"]'); - page.setDefaultTimeout(3000); await new Promise(x => setTimeout(x, 1000)); let terms_and_conditions_attached = await page.evaluate(() => document.body.textContent); if (terms_and_conditions_attached.indexOf("Terms and Conditions") > -1) { diff --git a/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js b/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js index 29dffd559..6f6e9d0a1 100644 --- a/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/1219_custom_thank_you_url.spec.js @@ -6,6 +6,7 @@ test.describe("order free plan tests:", () => { await admin_login(page); await page.goto('/admin/change-thank-you-url'); + await page.reload(); // ensure refresh await page.fill('#custom_thank_you_url', 'https://www.google.com'); await page.click('role=button[name="Save"]'); const custom_url = await page.textContent('text="Custom thank you url changed to: https://www.google.com"'); diff --git a/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js b/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js index a8e879681..57ace24e7 100644 --- a/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js @@ -237,7 +237,7 @@ test.describe("Plan Creation tests:", () => { //add first option console.log("adding options...") await page.goto('/admin/list-choice-groups'); - await page.click("text=Options"); + await page.getByRole('button', { name: 'Options' }).click(); await page.click("text=Add Option"); await page.fill('.form-control', 'Red'); diff --git a/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js b/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js index fe5b739bd..59026e2c0 100644 --- a/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/133_subscriber_order_plan_with_cooling_off.spec.js @@ -87,8 +87,8 @@ test.describe("order plan with cooling off:", () => { await page.goto('/admin/upcoming-invoices'); // Fetch Upcoming Invoices await fetch_upcomming_invoices(page); - const content_upcoming_invoice_plan_price_interval = await page.textContent('.plan-price-interval'); - expect(content_upcoming_invoice_plan_price_interval === '£10.00'); + await expect(page.locator("li >> text=£10.00").first()).toBeVisible(); + await expect(page.locator("li >> text=Cooling off plan").first()).toBeVisible(); const content_upcoming_invoice_plan_sell_price = await page.textContent('.upcoming-invoices-plan-no-sell_price'); expect(content_upcoming_invoice_plan_sell_price === '(No up-front cost)'); diff --git a/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js b/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js index f463b960c..4abe4e021 100644 --- a/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/147_shop_owner_pause_resume_and_cancel_subscriptions.spec.js @@ -32,11 +32,16 @@ test.describe("Pause, Resume and Cancel Subscription:", () => { const subscription_pause_notification = await page.textContent('text="Subscription paused"'); expect(subscription_pause_notification === "Subscription paused"); + // Click Refresh Subscription + await page.click('#refresh_subscriptions'); // this is the refresh subscription + await page.textContent('.alert-heading') === "Notification"; + await new Promise(x => setTimeout(x, 3000)); // 3 secconds + await page.goto('admin/subscribers') //Resume Subscription await page.click('.resume-action'); - await new Promise(x => setTimeout(x, 3000)); // 3 secconds + await new Promise(x => setTimeout(x, 3000)); // 3 seconds await page.click('.resume-yes'); await new Promise(x => setTimeout(x, 3000)); // 3 seconds const subscription_resume_notification = await page.textContent('text="Subscription resumed"'); diff --git a/tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js b/tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js deleted file mode 100644 index 6ff515ed0..000000000 --- a/tests/browser-automated-tests-playwright/e2e/shop_owner_pause_resume_and_cancel_subscriptions.js +++ /dev/null @@ -1,73 +0,0 @@ -const { test, expect } = require('@playwright/test'); -const SUBSCRIBER_EMAIL_USER = process.env.TEST_SUBSCRIBER_EMAIL_USER; -//Subscribie tests -test.describe("Pause, Resume and Cancel Subscription:", () => { - test("@147@shop-owner@Pause and Resume transaction", async ({ page }) => { - console.log("Pause and Resume transaction"); - // Go to My Subscribers page - // Crude wait before we check subscribers to allow webhooks time - await new Promise(x => setTimeout(x, 1000)); // 5 secconds - await page.goto('admin/subscribers') - - // Verify that subscriber is present in the list - const subscriber_email_content = await page.textContent('.subscriber-email'); - expect(subscriber_email_content === SUBSCRIBER_EMAIL_USER); - - const subscriber_subscription_title_content = await page.textContent('.subscription-title'); - expect(subscriber_subscription_title_content === 'Hair Gel'); - - // Verify if subscription active & click cancel - const subscription_status = await page.textContent('.subscription-status'); - expect(subscription_status === "active"); - - //Pause Subscription - await page.click('.pause-action'); - await new Promise(x => setTimeout(x, 3000)); // 3 seconds - await page.click('.pause-yes'); - await new Promise(x => setTimeout(x, 3000)); // 3 seconds - const subscription_pause_notification = await page.textContent('text="Subscription paused"'); - expect(subscription_pause_notification === "Subscription paused"); - expect(await page.screenshot()).toMatchSnapshot('paused-plan.png'); - - await new Promise(x => setTimeout(x, 3000)); // 3 secconds - - //Resume Subscription - await page.click('.resume-action'); - await new Promise(x => setTimeout(x, 3000)); // 3 secconds - await page.click('.resume-yes'); - await new Promise(x => setTimeout(x, 3000)); // 3 seconds - const subscription_resume_notification = await page.textContent('text="Subscription resumed"'); - expect(subscription_resume_notification === "Subscription resumed"); - expect(await page.screenshot()).toMatchSnapshot('resume-plan.png'); - - }); - test("479@shop-owner@Cancel transaction", async ({ page }) => { - // Go to My Subscribers page - // Crude wait before we check subscribers to allow webhooks time - await new Promise(x => setTimeout(x, 1000)); // 5 secconds - await page.goto('admin/subscribers'); - - // Verify that subscriber is present in the list - const subscriber_email_content = await page.textContent('.subscriber-email'); - expect(subscriber_email_content === SUBSCRIBER_EMAIL_USER); - - const subscriber_subscription_title_content = await page.textContent('.subscription-title'); - expect(subscriber_subscription_title_content === 'Hair Gel'); - - // Verify if subscription active & click cancel - const subscription_status = await page.textContent('.subscription-status'); - expect(subscription_status === "active"); - - //Cancel Subscription - await page.click('.cancel-action'); - await new Promise(x => setTimeout(x, 3000)); // 3 secconds - - await page.click('.cancel-yes'); - await new Promise(x => setTimeout(x, 3000)); // 3 seconds - - const subscription_canceled_notification = await page.textContent('text="Subscription cancelled"'); - expect(subscription_canceled_notification === "Subscription cancelled"); - expect(await page.screenshot()).toMatchSnapshot('cancel-plan.png'); - - }); -}); diff --git a/tests/browser-automated-tests-playwright/e2e/shop_owner_plan_creation.js b/tests/browser-automated-tests-playwright/e2e/shop_owner_plan_creation.js deleted file mode 100644 index b0432978b..000000000 --- a/tests/browser-automated-tests-playwright/e2e/shop_owner_plan_creation.js +++ /dev/null @@ -1,305 +0,0 @@ -const { test, expect } = require('@playwright/test'); - -//Subscribie tests -test.describe("Plan Creation tests:", () => { - // Create cooling off plan - test("@133@show-owner@Create cooling off plan", async ({ page }) => { - console.log("Starting plan creations..."); - await page.goto('/'); - try { - const cooling_off_plan_exist = await page.textContent('text="Cooling off plan"'); - if (cooling_off_plan_exist === 'Cooling off plan') { - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('Cooling-off-plan-already-created.png'); - console.log("Cooling off plan already created, exiting test"); - return 0; - } - } catch (e) { - console.log("Continuing with Cooling off plan creation"); - } - // Go to add plan page - await page.goto('/admin/add'); - - //Fill plan - await page.fill('#title-0', 'Cooling off plan'); - await page.fill('#selling_points-0-0', 'cooling'); - await page.fill('#selling_points-0-1', 'off'); - await page.fill('#selling_points-0-3', 'plan'); - await page.click('.form-check-input'); - - //wait for the recurring charge to expand - const monthly_content = await page.textContent('#interval_amount_label'); - expect(monthly_content === "Recurring Amount"); - await page.fill('#interval_amount-0', '10'); - await page.fill('#days_before_first_charge-0', '10'); - - await page.click('text="Save"'); - await page.goto("/"); - - //verify home page plan creation - await new Promise(x => setTimeout(x, 1000)); // 1 secconds - const cooling_off_plan = await page.textContent('text="Cooling off plan"'); - expect(cooling_off_plan === 'Cooling off plan'); - // screenshot cooling off plan - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('add-cooling-off-plan.png'); - - }); - test("@475@shop-owner@Create free trial plan", async ({ page }) => { - await page.goto('/'); - try { - const free_trial = await page.textContent('text="Free Trial plan"'); - if (free_trial === 'Free Trial plan') { - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('Free-trial-plan-already-created.png'); - console.log("Free trial plan already created, exiting test"); - return 0; - } - } catch (e) { - console.log("Continuing with Free trial plan creation"); - } - // Go to add plan - await page.goto('/admin/add'); - - //Fill plan - await page.fill('#title-0', 'Free Trial plan'); - await page.fill('#selling_points-0-0', 'Trial'); - await page.fill('#selling_points-0-1', 'Trial'); - await page.fill('#selling_points-0-3', 'Trial'); - - await page.click('.form-check-input'); - //wait for the recurring charge to expand - const monthly_content = await page.textContent('#interval_amount_label'); - expect(monthly_content === "Recurring Amount"); - - await page.fill('#interval_amount-0', '10'); - await page.fill('#trial_period_days-0', '10'); - - await page.click('text="Save"'); - await page.goto('/'); - - const free_trial = await page.textContent('text="Free Trial plan"'); - expect(free_trial === "Free Trial plan"); - - // screenshot cooling off plan - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('add-free-trial-plan.png'); - - }); - test("@516@shop-owner@Create cancel at plan", async ({ page }) => { - await page.goto('/'); - try { - const free_trial = await page.textContent('text="Automatically cancels on: 09-07-2025"'); - if (free_trial === "Automatically cancels on: 09-07-2025") { - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('cancel-at-plan-already-created.png'); - console.log("Cancel At plan already created, exiting test"); - return 0; - } - } catch (e) { - console.log("Continuing with Cancel At plan creation"); - } - // Go to add plan - await page.goto('/admin/add'); - - //Fill plan - await page.fill('#title-0', 'cancel at plan'); - await page.fill('#selling_points-0-0', 'plan'); - await page.fill('#selling_points-0-1', 'plan'); - await page.fill('#selling_points-0-3', 'plan'); - - await page.click('.form-check-input'); - //wait for the recurring charge to expand - const monthly_content = await page.textContent('#interval_amount_label'); - expect(monthly_content === "Recurring Amount"); - await page.fill('#interval_amount-0', '10'); - - // wait for the cancel at to expand - await page.click('#cancel_at_set-0'); - await new Promise(x => setTimeout(x, 1000)); - - // filling cancel at - await page.fill('[type=date]', '2025-07-09'); - - await page.click('text="Save"'); - await page.goto('/'); - - const free_trial = await page.textContent('text="Automatically cancels on: 09-07-2025"'); - expect(free_trial === "Automatically cancels on: 07-09-2025"); - - //screenshot - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('add-cancel-at-plan.png'); - - }); - test("@491@shop-owner@Create Private Plan", async ({ page }) => { - console.log("Creating Private Plan"); - await page.goto('/admin/edit'); - try { - const private_plan__already_exist = await page.textContent('text="First Private plan"'); - if (private_plan__already_exist === 'First Private plan') { - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('Private-plan-already-created.png'); - console.log("Private plan already created, exiting test"); - return 0; - } - } catch (e) { - console.log("Continuing with Private plan creation"); - } - // Go to add plan page - await page.goto('/admin/add'); - - //Fill plan - await page.fill('#title-0', 'First Private plan'); - await page.fill('#selling_points-0-0', 'This is a'); - await page.fill('#selling_points-0-1', 'Private'); - await page.fill('#selling_points-0-3', 'plan'); - await page.click('.form-check-input'); - - //wait for the recurring charge to expand - const monthly_content = await page.textContent('#interval_amount_label'); - expect(monthly_content === "Recurring Amount"); - await page.fill('#interval_amount-0', '15'); - - await page.click('#private'); - await page.click('text="Save"'); - - await page.goto('/admin/edit'); - const private_plan_exist = await page.textContent('text="First Private plan"'); - if (private_plan_exist === 'FIrst Private plan') { - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('Private-plan-was-created.png'); - console.log("Private plan was created, exiting test"); - } - await page.goto('/'); - let private_plan_content = await page.evaluate(() => document.body.textContent); - if (private_plan_content.indexOf("Private plan") > 1) { - console.log("ERROR: Private plan is not Private") - } - else { - console.log("Private plan is not in home page (Success)") - } - }); - - test("@264@shop-owner@Create plan with options, choice, required description", async ({ page }) => { - await page.goto('/'); - try { - const check_plan_with_choice_and_options = await page.textContent('text="Plan with choice and options"'); - expect(check_plan_with_choice_and_options === "Plan with choice and options"); - await page.click("text=See choice options"); - await page.click("text=Choices (2 options)"); - //check if plan options are blue and red - const check_plan_option_red = await page.textContent('text="Red"'); - expect(check_plan_option_red === "Red"); - const check_plan_option_blue = await page.textContent('text="Blue"'); - expect(check_plan_option_blue === "Blue"); - - const expand_choice = await page.textContent('text="Choices (2 options)"'); - if (expand_choice === "Choices (2 options)") { - - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('plan-with-choice-and-options-already-created.png'); - console.log("Plan with choice and options already created, exiting test"); - return 0; - } - } catch (e) { - console.log("Continuing with Plan with choice and options creation"); - } - // Go to add plan - await page.goto('/admin/add'); - - //Fill plan - await page.fill('#title-0', 'Plan with choice and options'); - await page.fill('#selling_points-0-0', 'Plan with '); - await page.fill('#selling_points-0-1', 'Choice and options'); - await page.fill('#selling_points-0-3', 'with required description'); - - //wait for the recurring charge to expand - await page.click('.form-check-input'); - const monthly_content = await page.textContent('#interval_amount_label'); - expect(monthly_content === "Recurring Amount"); - await page.fill('#interval_amount-0', '15'); - - //fill plan description - await page.click('#plan_description_required-0'); - await page.fill('#description-0', 'This plan requires a user description, options and choice selection'); - - //Require customer note - await page.click('#note_to_seller_required-0'); - await page.fill('#note_to_buyer_message-0', 'Please add another colour'); - - await page.click('text="Save"'); - //Check if plan was created - await page.goto('/'); - const plan_with_choice_and_options = await page.textContent('text="Plan with choice and options"'); - expect(plan_with_choice_and_options === "Plan with choice and options"); - //screenshot - await new Promise(x => setTimeout(x, 1000)); - expect(await page.screenshot()).toMatchSnapshot('plan-with-choice-and-options-created.png'); - - //Add options and choices - console.log('adding plan options and choices'); - await page.goto('/admin/add-choice-group'); - - //add choice group - const add_choice_group = await page.textContent('text="Add Choice Group"'); - expect(add_choice_group === "Add Choice Group"); - await page.fill('.form-control', 'Choices'); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("Choice Created"); - - //add first option - console.log("adding options...") - await page.goto('/admin/list-choice-groups'); - await page.click("text=Options"); - - const add_options = await page.textContent('text="Option"'); - expect(add_options === "Option"); - await page.click("text=Add Option"); - await page.fill('.form-control', 'Red'); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("First Option added"); - - //add second option - await page.click("text=Add Option"); - await page.fill('.form-control', 'Blue'); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("Second Option added"); - - //assign choice to plan - console.log("Assigning Choice to plan...") - await page.goto('/admin/list-choice-groups'); - await page.click("text=Assign Plan"); - const assign_choice_to_plan = await page.textContent('text="Choice Group - Assign Plan"'); - expect(assign_choice_to_plan === "Choice Group - Assign Plan"); - await page.click("text=Plan with choice and options"); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("Choice assigned to plan"); - - //confirm choice and option plan was added - await page.goto('/'); - const free_trial = await page.textContent('text="Plan with choice and options"'); - expect(free_trial === "Plan with choice and options"); - - //check if plan options are blue and red - const check_plan_option_red = await page.textContent('text="Red"'); - expect(check_plan_option_red === "Red"); - const check_plan_option_blue = await page.textContent('text="Blue"'); - expect(check_plan_option_blue === "Blue"); - - //check if plan have choice - await page.click("text=See choice options"); - await page.click("text=Choices (2 options)"); - const expand_choice = await page.textContent('text="Choices (2 options)"'); - expect(expand_choice === "Choices (2 options)"); - expect(await page.screenshot()).toMatchSnapshot('plan-with-choice-and-options-created.png'); - console.log("Plan with option and choices shown in homepage"); - }); - -}); - -//module.exports = plan_creation; diff --git a/tests/browser-automated-tests-playwright/e2e/shop_owner_transaction_filter_by_name_and_by_plan_title.js b/tests/browser-automated-tests-playwright/e2e/shop_owner_transaction_filter_by_name_and_by_plan_title.js deleted file mode 100644 index 78ae15033..000000000 --- a/tests/browser-automated-tests-playwright/e2e/shop_owner_transaction_filter_by_name_and_by_plan_title.js +++ /dev/null @@ -1,36 +0,0 @@ -const { test, expect } = require('@playwright/test'); -test("@619@shop-owner@transaction filter by name and by plan title", async ({ page }) => { - console.log("transaction filter by name and by plan title"); - - await page.goto("admin/dashboard") - const content = await page.textContent('.card-title') - expect(content === 'Checklist'); // If we see "Checklist", we're logged in to admin - // Verify transaction is present in 'All transactions page' - - await page.goto('admin/transactions') - const transaction_content = await page.textContent('.transaction-amount'); - expect (transaction_content == '£6.99'); - - // Verify subscriber is linked to the transaction: - const transaction_subscriber_content = await page.textContent('.transaction-subscriber'); - expect (transaction_subscriber_content === 'John smith'); - // Verify search by Name - Transaccions - await page.fill('input[name=subscriber_name]',"John"); - await page.click('.btn-primary'); - expect (transaction_subscriber_content === 'John smith'); - expect(await page.screenshot()).toMatchSnapshot('filter-by-name.png'); - - // Verify search by plan title - await page.fill('input[name=plan_title]',"Hair"); - await page.click('.btn-primary'); - expect (transaction_subscriber_content === 'John smith'); - expect(await page.screenshot()).toMatchSnapshot('filter-by-plan.png'); - - // Verify search by Name & plan title - await page.fill('input[name=subscriber_name]',"John"); - await page.fill('input[name=plan_title]',"Hair"); - await page.click('.btn-primary'); - expect (transaction_subscriber_content === 'John smith'); - expect(await page.screenshot()).toMatchSnapshot('filter-by-name-and-plan.png'); -}); - diff --git a/tests/browser-automated-tests-playwright/playwright.config.ts b/tests/browser-automated-tests-playwright/playwright.config.ts index 46422c03c..d5561ff2b 100644 --- a/tests/browser-automated-tests-playwright/playwright.config.ts +++ b/tests/browser-automated-tests-playwright/playwright.config.ts @@ -54,6 +54,7 @@ export default defineConfig({ }, video: 'on', + navigationTimeout: 1 * 60 * 1000, // 1 minutes, }, /* Configure projects for major browsers */ @@ -62,9 +63,5 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, ], }); From 8dfb48e48f3c39f4760fe3c143bd76fdf7237f01 Mon Sep 17 00:00:00 2001 From: chrisjsimpson Date: Tue, 7 Jan 2025 00:01:27 +0000 Subject: [PATCH 11/11] #1431 correct flakey tests, increase fetch upcoming invoices wait time (to give time for invoices to be created at stripes end --- .../e2e/133_shop_owner_plan_creation.spec.js | 82 +++++++++++-------- .../212_shop_owner_slogan_creation.spec.js | 3 +- ...h_choice_options_and_required_note.spec.js | 1 + .../e2e/features/admin_login.js | 1 + .../e2e/features/fetch_upcomming_invoices.js | 5 +- 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js b/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js index 57ace24e7..4371f92d4 100644 --- a/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/133_shop_owner_plan_creation.spec.js @@ -169,7 +169,6 @@ test.describe("Plan Creation tests:", () => { } }); - test("@264@shop-owner @264_shop_owner_create_plan_with_choice_options", async ({ page }) => { await admin_login(page); await set_test_name_cookie(page, "@264_shop_owner_create_plan_with_choice_options") @@ -224,44 +223,54 @@ test.describe("Plan Creation tests:", () => { //Add options and choices console.log('adding plan options and choices'); - await page.goto('/admin/add-choice-group'); - - //add choice group + await page.goto('/admin/list-choice-groups'); const add_choice_group = await page.textContent('text="Add Choice Group"'); expect(add_choice_group === "Add Choice Group"); - await page.fill('.form-control', 'Choices'); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("Choice Created"); - //add first option - console.log("adding options...") - await page.goto('/admin/list-choice-groups'); - await page.getByRole('button', { name: 'Options' }).click(); - - await page.click("text=Add Option"); - await page.fill('.form-control', 'Red'); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("First Option added"); - - //add second option - await page.click("text=Add Option"); - await page.fill('.form-control', 'Blue'); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("Second Option added"); - - //assign choice to plan - console.log("Assigning Choice to plan...") - await page.goto('/admin/list-choice-groups'); - await page.click("text=Assign Plan"); - const assign_choice_to_plan = await page.textContent('text="Choice Group - Assign Plan"'); - expect(assign_choice_to_plan === "Choice Group - Assign Plan"); - await page.click("text=Plan with choice and options"); - await page.click("text='Save'"); - await page.textContent('.alert-heading') === "Notification"; - console.log("Choice assigned to plan"); + // Check if choice group named 'Choices' already exists + try { + const check_choice_group = await page.textContent('text="Choices"', { timeout: 2000 }); + if (check_choice_group === "Choices") { + console.log("Choice Group already created."); + } + } catch (e) { + console.log("Choice Group 'Choices' does not exist, continuing with creation"); + //add choice group + await page.goto('/admin/add-choice-group'); + await page.fill('.form-control', 'Choices'); + await page.click("text='Save'"); + await page.textContent('.alert-heading') === "Notification"; + console.log("Choice Created"); + + //add first option + console.log("adding options...") + await page.goto('/admin/list-choice-groups'); + await page.getByRole('button', { name: 'Options' }).first().click(); + + await page.click("text=Add Option"); + await page.fill('.form-control', 'Red'); + await page.click("text='Save'"); + await page.textContent('.alert-heading') === "Notification"; + console.log("First Option added"); + + //add second option + await page.click("text=Add Option"); + await page.fill('.form-control', 'Blue'); + await page.click("text='Save'"); + await page.textContent('.alert-heading') === "Notification"; + console.log("Second Option added"); + + //assign choice to plan + console.log("Assigning Choice to plan...") + await page.goto('/admin/list-choice-groups'); + await page.click("text=Assign Plan"); + const assign_choice_to_plan = await page.textContent('text="Choice Group - Assign Plan"'); + expect(assign_choice_to_plan === "Choice Group - Assign Plan"); + await page.click("text=Plan with choice and options"); + await page.click("text='Save'"); + await page.textContent('.alert-heading') === "Notification"; + console.log("Choice assigned to plan"); + } //confirm choice and option plan was added await page.goto('/'); @@ -280,6 +289,7 @@ test.describe("Plan Creation tests:", () => { const expand_choice = await page.textContent('text="Choices (2 options)"'); expect(expand_choice === "Choices (2 options)"); console.log("Plan with option and choices shown in homepage"); + }); }); diff --git a/tests/browser-automated-tests-playwright/e2e/212_shop_owner_slogan_creation.spec.js b/tests/browser-automated-tests-playwright/e2e/212_shop_owner_slogan_creation.spec.js index d982c968b..7f49c8ce4 100644 --- a/tests/browser-automated-tests-playwright/e2e/212_shop_owner_slogan_creation.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/212_shop_owner_slogan_creation.spec.js @@ -23,8 +23,7 @@ test("@212@shop-owner@slogan creation @212_shop_owner_slogan_creation", async ({ //verify home page plan creation await page.goto("/"); - await new Promise(x => setTimeout(x, 1000)); // 1 secconds + await page.reload(); const slogan_created = await page.textContent('text=this is a slogan'); expect(slogan_created === 'this is a slogan'); - // TODO screenshot cooling off plan }); \ No newline at end of file diff --git a/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js b/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js index 4eff77c69..a1a70595d 100644 --- a/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js +++ b/tests/browser-automated-tests-playwright/e2e/264_subscriber_order_plan_with_choice_options_and_required_note.spec.js @@ -91,6 +91,7 @@ test("@264@subscriber @264_subscriber_order_plan_with_choice_options_and_require await page.goto('/admin/upcoming-invoices'); // Fetch Upcoming Invoices await fetch_upcomming_invoices(page); + await page.reload(); await page.locator('css=span.plan-price-interval', {hasText: "£15.00"}).first().textContent() === '£15.00'; const content_upcoming_invoice_plan_sell_price = await page.textContent('.upcoming-invoices-plan-no-sell_price'); diff --git a/tests/browser-automated-tests-playwright/e2e/features/admin_login.js b/tests/browser-automated-tests-playwright/e2e/features/admin_login.js index a7cbf5e71..dff37cb04 100644 --- a/tests/browser-automated-tests-playwright/e2e/features/admin_login.js +++ b/tests/browser-automated-tests-playwright/e2e/features/admin_login.js @@ -3,6 +3,7 @@ var admin_login = async function (page) { await page.fill('#email', 'admin@example.com'); await page.fill('#password', 'password'); await page.click('#login'); + await page.goto(process.env['PLAYWRIGHT_HOST'] + '/admin/dashboard'); } diff --git a/tests/browser-automated-tests-playwright/e2e/features/fetch_upcomming_invoices.js b/tests/browser-automated-tests-playwright/e2e/features/fetch_upcomming_invoices.js index 7ee24aa01..9a4c8849f 100644 --- a/tests/browser-automated-tests-playwright/e2e/features/fetch_upcomming_invoices.js +++ b/tests/browser-automated-tests-playwright/e2e/features/fetch_upcomming_invoices.js @@ -1,9 +1,10 @@ var fetch_upcomming_invoices = async function (page) { // Go to upcoming payments and ensure plan is attached to upcoming invoice await page.goto('/admin/upcoming-invoices'); - await new Promise(x => setTimeout(x, 25000)); + await new Promise(x => setTimeout(x, 35000)); await page.click('#fetch_upcoming_invoices'); - await new Promise(x => setTimeout(x, 20000)); + await new Promise(x => setTimeout(x, 30000)); + await page.reload(); } module.exports.fetch_upcomming_invoices = fetch_upcomming_invoices; \ No newline at end of file