From aae666a22f841a73f6ca0f8a349abac80b0d322d Mon Sep 17 00:00:00 2001 From: petechd <53475968+petechd@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:06:33 +0000 Subject: [PATCH] Add automatic integration test generation script (#1316) Co-authored-by: Liam Toozer --- Makefile | 5 + Pipfile | 1 + Pipfile.lock | 124 +++++++++++++++++---- README.md | 5 + app/translations/messages.pot | 22 ++-- mypy.ini | 5 + scripts/README.md | 40 +++++++ scripts/generate_integration_test.py | 146 +++++++++++++++++++++++++ scripts/merge_profiles.py | 3 +- scripts/run_mypy.sh | 2 +- tests/integration/test_header_links.py | 2 +- 11 files changed, 322 insertions(+), 33 deletions(-) create mode 100644 scripts/README.md create mode 100644 scripts/generate_integration_test.py diff --git a/Makefile b/Makefile index 9f01ddaae9..239cf0ac3c 100644 --- a/Makefile +++ b/Makefile @@ -116,3 +116,8 @@ dev-compose-down-linux: profile: pipenv run python profile_application.py + +generate-integration-test: + pipenv run playwright install chromium + pipenv run python -m scripts.generate_integration_test + pipenv run black ./scripts/test_* diff --git a/Pipfile b/Pipfile index 1edd8c5a6a..07b6148af8 100644 --- a/Pipfile +++ b/Pipfile @@ -38,6 +38,7 @@ types-python-dateutil = "*" pytest-mock = "*" types-cachetools = "*" types-pytz = "*" +playwright = "*" [packages] colorama = "*" diff --git a/Pipfile.lock b/Pipfile.lock index ae499cb591..bf4b748975 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b857953d9c0ba947a86596e0beb665969ae68afee425c3350a0dd4049a927daa" + "sha256": "408dbd7bc01df9a590c2f82b3818b9cc68d7e98e5f11b78350f75d87d706ee4c" }, "pipfile-spec": 6, "requires": { @@ -43,20 +43,20 @@ }, "boto3": { "hashes": [ - "sha256:300888f0c1b6f32f27f85a9aa876f50f46514ec619647af7e4d20db74d339714", - "sha256:b26928f9a21cf3649cea20a59061340f3294c6e7785ceb6e1a953eb8010dc3ba" + "sha256:c26c31ceeeb2bc5d2bb96ba0fdc9a04d7b10e6e0b081c55b9cea9069a0be04dd", + "sha256:f8046e3e2d1186a49b49f7464c4811c265c86001f404dd1a96c4365c773a4245" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.56" + "version": "==1.34.57" }, "botocore": { "hashes": [ - "sha256:bffeb71ab21d47d4ecf947d9bdb2fbd1b0bbd0c27742cea7cf0b77b701c41d9f", - "sha256:fff66e22a5589c2d58fba57d1d95c334ce771895e831f80365f6cff6453285ec" + "sha256:9a5aa2034de9f0c367b4b61a92af0fa827f5c21affa19e0a284838a142e71083", + "sha256:c8dafe0ad378a88bcf4153e6972870b03fb5aab406b694202307500709940baf" ], "markers": "python_version >= '3.8'", - "version": "==1.34.56" + "version": "==1.34.57" }, "brotli": { "hashes": [ @@ -608,12 +608,12 @@ }, "google-cloud-pubsub": { "hashes": [ - "sha256:48c8e17a8168c43e3188635cbd9e07fbe3004120433712ce84b3a04bbf18c188", - "sha256:8c69ed04800f4f552cdf3b9028f06d9271ac6e60443b2308c984def442e69684" + "sha256:06dd62181e2f248f32b9077f4dc07b413191a84fc06d7323b208602d887207bc", + "sha256:b6d06f1827968273c42b57a09f642462649c9504dc0f8756f99770f4e3e755ad" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.20.0" + "version": "==2.20.1" }, "google-cloud-storage": { "hashes": [ @@ -944,11 +944,11 @@ }, "jwcrypto": { "hashes": [ - "sha256:59e7d5e4589d1b07170f368e20c32eb32a023911806a9723b1f43a0d8b3028d6", - "sha256:c18b10b2049603bef3ae7b77ad14bded431a9077d113447d62bebd8550b0d5bd" + "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", + "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039" ], "markers": "python_version >= '3.8'", - "version": "==1.5.5" + "version": "==1.5.6" }, "markupsafe": { "hashes": [ @@ -1534,20 +1534,20 @@ }, "boto3": { "hashes": [ - "sha256:300888f0c1b6f32f27f85a9aa876f50f46514ec619647af7e4d20db74d339714", - "sha256:b26928f9a21cf3649cea20a59061340f3294c6e7785ceb6e1a953eb8010dc3ba" + "sha256:c26c31ceeeb2bc5d2bb96ba0fdc9a04d7b10e6e0b081c55b9cea9069a0be04dd", + "sha256:f8046e3e2d1186a49b49f7464c4811c265c86001f404dd1a96c4365c773a4245" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.56" + "version": "==1.34.57" }, "botocore": { "hashes": [ - "sha256:bffeb71ab21d47d4ecf947d9bdb2fbd1b0bbd0c27742cea7cf0b77b701c41d9f", - "sha256:fff66e22a5589c2d58fba57d1d95c334ce771895e831f80365f6cff6453285ec" + "sha256:9a5aa2034de9f0c367b4b61a92af0fa827f5c21affa19e0a284838a142e71083", + "sha256:c8dafe0ad378a88bcf4153e6972870b03fb5aab406b694202307500709940baf" ], "markers": "python_version >= '3.8'", - "version": "==1.34.56" + "version": "==1.34.57" }, "certifi": { "hashes": [ @@ -1913,6 +1913,70 @@ "markers": "python_version >= '3.7'", "version": "==1.4.0" }, + "greenlet": { + "hashes": [ + "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", + "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6", + "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257", + "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4", + "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", + "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61", + "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", + "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca", + "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7", + "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728", + "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", + "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", + "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", + "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414", + "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04", + "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a", + "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf", + "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", + "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", + "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e", + "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274", + "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb", + "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b", + "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9", + "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b", + "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", + "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506", + "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405", + "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113", + "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f", + "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5", + "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", + "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", + "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f", + "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a", + "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", + "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", + "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6", + "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d", + "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71", + "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", + "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", + "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", + "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067", + "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc", + "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881", + "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3", + "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", + "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac", + "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53", + "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0", + "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b", + "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83", + "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41", + "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c", + "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", + "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", + "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" + ], + "markers": "python_version >= '3.11' and platform_python_implementation == 'CPython'", + "version": "==3.0.3" + }, "httmock": { "hashes": [ "sha256:13e6c63f135a928e15d386af789a2890efb03e0e280f29bdc9961f3f0dc34cb9", @@ -2197,6 +2261,20 @@ "markers": "python_version >= '3.8'", "version": "==4.2.0" }, + "playwright": { + "hashes": [ + "sha256:283887f0bdd0039c3d720e32fbc73a045c24fa800599a6ad60fb199c29580534", + "sha256:313f2551a772f57c9ccca017c4dd4661f2277166f9e1d84bbf5a2e316f0f892c", + "sha256:4e1fc1c049a0af64626ddd50814d14a01f316bcbb4d1aa83c3416fe420add558", + "sha256:b2a46a24641e5d468046cde567c98fdb8d85e32df901630b14dfb288cbd1ed4f", + "sha256:dbf473496808d4c2c816902c1dee2aabc029648e56ce8514b643f5a1a6fc8e22", + "sha256:e092c6cfbf797bff03fbdfc53c3e6a9e29fbcf6b82f9e43113d37494aee0561b", + "sha256:e2b293f077efeaa45253fde31cea4bc6b0ae8be6b5e65e8ce8b4aa3b9f0d55b6" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.42.0" + }, "pluggy": { "hashes": [ "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", @@ -2220,6 +2298,14 @@ ], "version": "==2.21" }, + "pyee": { + "hashes": [ + "sha256:9bcc9647822234f42c228d88de63d0f9ffa881e87a87f9d36ddf5211f6ac977d", + "sha256:a642c51e3885a33ead087286e35212783a4e9b8d6514a10a5db4e57ac57b2b29" + ], + "markers": "python_version >= '3.8'", + "version": "==11.0.1" + }, "pyflakes": { "hashes": [ "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", diff --git a/README.md b/README.md index 46975c88cf..c700ff6c37 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,11 @@ Or set the `GOOGLE_CLOUD_PROJECT` environment variable to your gcp project id. --- + +## Integration Tests +There is a dev-convenience script that auto generates the lines of code for a user journey. See [README](scripts/README.md) for more information and how to run +the script. + ## Frontend Tests The frontend tests use NodeJS to run. To handle different versions of NodeJS it is recommended to install `Node Version Manager` (`nvm`). It is similar to pyenv but for Node versions. diff --git a/app/translations/messages.pot b/app/translations/messages.pot index 700a61f344..fa30249d65 100644 --- a/app/translations/messages.pot +++ b/app/translations/messages.pot @@ -249,55 +249,55 @@ msgid_plural "{number_of_days} days" msgstr[0] "" msgstr[1] "" -#: app/routes/errors.py:159 +#: app/routes/errors.py:160 msgid "You have reached the maximum number of individual access codes" msgstr "" -#: app/routes/errors.py:162 +#: app/routes/errors.py:163 msgid "" "If you need more individual access codes, please contact us." msgstr "" -#: app/routes/errors.py:180 +#: app/routes/errors.py:181 msgid "You have reached the maximum number of times for submitting feedback" msgstr "" -#: app/routes/errors.py:183 +#: app/routes/errors.py:184 msgid "" "If you need to give more feedback, please contact us." msgstr "" -#: app/routes/errors.py:233 +#: app/routes/errors.py:232 msgid "Sorry, there was a problem sending the access code" msgstr "" -#: app/routes/errors.py:240 +#: app/routes/errors.py:239 msgid "You can try to request a new access code again." msgstr "" -#: app/routes/errors.py:243 app/routes/errors.py:268 app/routes/errors.py:290 +#: app/routes/errors.py:242 app/routes/errors.py:267 app/routes/errors.py:289 msgid "" "If this problem keeps happening, please contact us for help." msgstr "" -#: app/routes/errors.py:264 +#: app/routes/errors.py:263 msgid "Sorry, there was a problem sending the confirmation email" msgstr "" -#: app/routes/errors.py:265 +#: app/routes/errors.py:264 msgid "You can try to send the email again." msgstr "" -#: app/routes/errors.py:286 templates/errors/403.html:3 +#: app/routes/errors.py:285 templates/errors/403.html:3 #: templates/errors/403.html:6 templates/errors/submission-failed.html:5 #: templates/errors/submission-failed.html:8 msgid "Sorry, there is a problem" msgstr "" -#: app/routes/errors.py:287 +#: app/routes/errors.py:286 msgid "You can try to submit your feedback again." msgstr "" diff --git a/mypy.ini b/mypy.ini index 8918828886..0e98cd508a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -106,3 +106,8 @@ no_implicit_optional = True disallow_untyped_defs = True warn_return_any = True no_implicit_optional = True + +[mypy-scripts.generate_integration_test] +disallow_untyped_defs = True +warn_return_any = True +no_implicit_optional = True diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000000..0d055d48bb --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,40 @@ +# Scripts + +## Script to auto-generate code for integration test + +### Details + +To speed up the process of generating integration tests for Runner, there is a dev-convenience script that records the GET and POST requests of a user journey +and outputs this formatted in the style of an integration test. + +### Overview + +* All POSTs are recorded. To ensure only the necessary GET requests are recorded, additional logic excludes the following GET requests: + * Session tokens + * Initial URL requests for each page load +* Additional logic is in place to ensure that, when navigating backwards in a journey after following links (e.g. 'previous' link), it is recorded correctly. + This is achieved by storing the previous request method at module-level so that it can be used in deciding whether to record or disregard the GET request. +* You will need to manually add your assertions in the generated test file +* When the script is launched, it will create a new file for the schema chosen. If you launch the script again for the same schema, it will overwrite the + previous file output +* The script is intended to be run with schemas with a `test_` prefix, which would suit most scenarios for test generation. If you wish to use a schema without + this prefix, you will need to manually amend the generated names for the file, class, and function to allow pytest to process the test file correctly +* It does **not** handle dynamic answers because these are generated at runtime - you will need to update the output script to handle `list_item_id` separately, + as they will not be known beforehand + +### Usage + +Run the following make command from the project root folder: + +```shell +make generate-integration-test +``` + +This will pause the script and open a browser pointing to the Launcher UI (make sure the application and supporting services are running). Now follow the below +steps: + +1. Choose a schema and launch it - the schema name will be used for the name of the integration test output file +1. Navigate through the survey +1. When you're finished with the journey at any point, return to the command line and hit Enter +1. The output will be shown in the logs, and a formatted file will be created for you in the scripts folder. For example: `scripts/test_checkbox.py` +1. Add your assertions to the test file and move the file into the appropriate `test/integration/` location diff --git a/scripts/generate_integration_test.py b/scripts/generate_integration_test.py new file mode 100644 index 0000000000..449bd4997c --- /dev/null +++ b/scripts/generate_integration_test.py @@ -0,0 +1,146 @@ +from typing import IO, Dict +from urllib.parse import parse_qs, urlparse + +from playwright.sync_api import Playwright, Request, sync_playwright +from structlog import get_logger + +logger = get_logger() + +LAUNCHER_ROOT_URL = "http://localhost:8000" +RUNNER_ROOT_URL = "http://localhost:5000" + +TEST_TEMPLATE = """from tests.integration.integration_test_case import IntegrationTestCase + + +class Test{class_name}(IntegrationTestCase): + def test_{function_name}(self): + self.launchSurvey("{schema_name}") +""" + +survey_journey: Dict[str, str | bool | None] = { + "previous_request_method": None, + "in_progress": False, + "schema_name": None, +} + +output: Dict[str, str] = {"file_name": ""} + + +def process_runner_request(request: Request) -> None: + with open(output["file_name"], "a", encoding="utf-8") as file: + if request.method == "POST": + process_post(request, file) + + elif request.method == "GET": + process_get(request, file) + + +def process_post(request: Request, file: IO) -> None: + survey_journey["previous_request_method"] = "POST" + + # Playwright Request.post_data comes formatted like a URL query string, so can be parsed + post_data = parse_qs(request.post_data) + del post_data["csrf_token"] + + items = { + answer_id: answer_values[0] if len(answer_values) == 1 else answer_values + for answer_id, answer_values in post_data.items() + } + + # Post items, or empty post for no answers/non-question pages + file.write(generate_method_request(method="post", data=items or "")) + + +def is_recordable_survey_navigation(request: Request) -> bool: + return ( + survey_journey["previous_request_method"] == "GET" + and "session?token" not in request.url + and request.url != f"{RUNNER_ROOT_URL}/questionnaire/" + ) + + +def process_get(request: Request, file: IO) -> None: + """ + We only want to record GET requests in Runner for actions like navigating back in a survey journey. Therefore, we exclude the following: + - the very first GET action of a survey journey, after schema is loaded + - tokens/authentication + """ + has_journey_started = ( + not survey_journey["in_progress"] + and request.url == f"{RUNNER_ROOT_URL}/questionnaire/" + ) + if has_journey_started: + survey_journey["in_progress"] = True + return + + if is_recordable_survey_navigation(request): + path = f'"{urlparse(request.url).path}"' + file.write(generate_method_request(method="get", data=path)) + + elif survey_journey["in_progress"]: + # ensure the request method is captured - allows us to record Runner GET navigation actions on the next pass through + survey_journey["previous_request_method"] = request.method + + +def process_launcher_request(request: Request) -> None: + if request.method != "GET": + return + + if survey_journey["in_progress"]: + # capture launcher urls for sign-out, save etc + with open(output["file_name"], "a", encoding="utf-8") as file: + path = f'"{urlparse(request.url).path}"' + file.write(generate_method_request(method="get", data=path)) + else: + # start of journey, so create a skeleton file using the schema name + survey_journey["schema_name"] = parse_qs(request.url)["schema_name"][0] + output["file_name"] = f"./scripts/{survey_journey['schema_name']}.py" + + with open(output["file_name"], "w", encoding="utf-8") as file: + # Type ignore: schema_name is taken as string from query string + class_name = survey_journey["schema_name"].title().replace("_", "") # type: ignore + file.write( + TEST_TEMPLATE.format( + class_name=class_name, + function_name=survey_journey["schema_name"], + schema_name=survey_journey["schema_name"], + ) + ) + logger.info(request) + + +def generate_method_request(*, method: str, data: dict | str | None = None) -> str: + snippet = f"self.{method}({data})" + logger.info(f'Generating Runner code snippet for HTTP request: "{snippet}"') + return f"\n {snippet}" + + +def request_handler(request: Request) -> None: + if LAUNCHER_ROOT_URL in request.url: + process_launcher_request(request) + elif RUNNER_ROOT_URL in request.url: + process_runner_request(request) + + +def run(pw: Playwright) -> None: + chromium = pw.chromium + browser = chromium.launch(headless=False, args=["--start-maximized"]) + page = browser.new_page(no_viewport=True) + page.goto(LAUNCHER_ROOT_URL) + + page.on("request", request_handler) + + input( + "Script is paused. Start navigating through the browser for the journey & press Enter when finished to capture" + " the output and add into a test file\n" + ) + browser.close() + logger.info( + "Integration test generated successfully", + integration_test_file=output["file_name"], + ) + + +if __name__ == "__main__": + with sync_playwright() as playwright: + run(playwright) diff --git a/scripts/merge_profiles.py b/scripts/merge_profiles.py index 4f5c4e6ea3..452bb27c59 100644 --- a/scripts/merge_profiles.py +++ b/scripts/merge_profiles.py @@ -15,4 +15,5 @@ else: stats.add(profiling_dir + p) -stats.dump_stats("combined_profile.prof") +if stats: + stats.dump_stats("combined_profile.prof") diff --git a/scripts/run_mypy.sh b/scripts/run_mypy.sh index d3bdb34e1e..27a4f3ec42 100755 --- a/scripts/run_mypy.sh +++ b/scripts/run_mypy.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -pipenv run mypy app +pipenv run mypy app scripts diff --git a/tests/integration/test_header_links.py b/tests/integration/test_header_links.py index 92a11ff9fc..72d81f5053 100644 --- a/tests/integration/test_header_links.py +++ b/tests/integration/test_header_links.py @@ -165,7 +165,7 @@ def test_links_not_in_header_when_no_session(self): self.assert_sign_out_link_does_not_exist() self.assert_help_link_does_not_exist() - def test_links_not_in_header_when_valid_session_theme_social(self): + def test_links_not_in_header_when_valid_session_theme_social_thank_you_page(self): # Given self.launchSurveyV2(schema_name="test_theme_social", theme="social") self.post()