├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── devRun.yml │ └── nightly.yml ├── .gitignore ├── .husky └── pre-commit ├── LICENSE ├── README.md ├── SECURITY.md ├── biome.json ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── renovate.json ├── resources ├── data.xls ├── dog.png └── test.txt ├── src ├── entities │ └── UserData.ts ├── fixtures │ └── projectFixtures.ts ├── pages │ └── HomePage.ts ├── services │ ├── DatabaseService.ts │ ├── MailinatorService.ts │ ├── NetworkBlockerService.ts │ ├── SecureApiService.ts │ ├── SftpService.ts │ ├── VisualTrackerService.ts │ ├── launchDarkly │ │ ├── LaunchDarklyService.ts │ │ └── featureFlags.ts │ ├── s3Service.ts │ └── stripe │ │ ├── PaymentMethod.ts │ │ └── StripeService.ts └── utilities │ ├── constants.ts │ ├── environment.ts │ └── fileUtils.ts ├── tests ├── demo-todo-app.spec.ts ├── example.spec.ts ├── excel.spec.ts ├── filechooser.spec.ts ├── geolocation.spec.ts ├── launchdarkly.spec.ts ├── mailinator.spec.ts ├── s3Upload.spec.ts └── sftpUpload.spec.ts └── tsconfig.json /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | nirt236@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | buy_me_a_coffee: nirtal 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Screenshots (if appropriate): 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 22 | 23 | ## Checklist: 24 | 25 | 26 | - [ ] My code follows the code style of this project. 27 | - [ ] My change requires a change to the documentation. 28 | - [ ] I have updated the documentation accordingly. -------------------------------------------------------------------------------- /.github/workflows/devRun.yml: -------------------------------------------------------------------------------- 1 | name: Pre merge test 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | paths: 7 | - '**/*.ts' 8 | - '**/*.js' 9 | - 'package.json' 10 | - 'pnpm-lock.yaml' 11 | - 'tsconfig.json' 12 | - 'playwright.config.ts' 13 | - 'eslint.config.js' 14 | - '.github/workflows/devRun.yml' 15 | 16 | permissions: 17 | contents: write 18 | pages: write 19 | 20 | jobs: 21 | merge_test: 22 | timeout-minutes: 15 23 | runs-on: ubuntu-latest 24 | env: 25 | BASE_URL: ${{ vars.BASE_URL }} 26 | container: 27 | image: mcr.microsoft.com/playwright:v1.52.0 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Install pnpm 32 | uses: pnpm/action-setup@v4 33 | with: 34 | run_install: false 35 | - name: Install Node.js 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: lts/* 39 | cache: 'pnpm' 40 | - name: Install dependencies 41 | run: pnpm install --frozen-lockfile 42 | - name: Run Playwright tests 43 | run: xvfb-run pnpm exec playwright test --grep "@devRun" 44 | - name: Link Git Information And Browser Version To Allure Report 45 | working-directory: allure-results 46 | if: always() 47 | run: | 48 | git config --global --add safe.directory "$GITHUB_WORKSPACE" 49 | { 50 | echo BUILD_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} 51 | echo GIT_BRANCH=${{ github.head_ref || github.ref_name }} 52 | echo GIT_COMMIT_ID=${{ github.sha }} 53 | echo GIT_COMMIT_MESSAGE="$(git show -s --format=%s HEAD)" 54 | echo GIT_COMMIT_AUTHOR_NAME="$(git show -s --format='%ae' HEAD)" 55 | echo GIT_COMMIT_TIME="$(git show -s --format=%ci HEAD)" 56 | } >> environment.properties 57 | - name: Generate Allure Report 58 | uses: simple-elf/allure-report-action@v1.12 59 | if: always() 60 | id: allure-report 61 | with: 62 | allure_results: allure-results 63 | allure_report: allure-report 64 | gh_pages: gh-pages 65 | allure_history: allure-history 66 | - name: Deploy Report To Github Pages 67 | if: always() 68 | uses: peaceiris/actions-gh-pages@v4 69 | with: 70 | github_token: ${{ secrets.GITHUB_TOKEN }} 71 | publish_dir: allure-history 72 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly regression tests 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | inputs: 8 | test_command: 9 | description: 'Custom test command' 10 | required: true 11 | default: '--grep-invert "@devRun"' 12 | type: string 13 | parallelism: 14 | description: 'Number of machines to split tests' 15 | required: false 16 | default: 2 17 | type: number 18 | 19 | permissions: 20 | contents: write 21 | pages: write 22 | 23 | jobs: 24 | setup-matrix: 25 | runs-on: ubuntu-latest 26 | outputs: 27 | matrix: ${{ steps.set-matrix.outputs.matrix }} 28 | steps: 29 | - id: set-matrix 30 | run: | 31 | count=${{ github.event.inputs.parallelism || 2 }} 32 | matrix=$(seq -s ',' 1 $count) 33 | echo "matrix=$(jq -cn --argjson groups "[$matrix]" '{group: $groups}')" >> $GITHUB_OUTPUT 34 | 35 | nightly-test: 36 | needs: setup-matrix 37 | timeout-minutes: 15 38 | runs-on: ubuntu-latest 39 | container: 40 | image: mcr.microsoft.com/playwright:v1.52.0 41 | env: 42 | BASE_URL: ${{ vars.BASE_URL }} 43 | strategy: 44 | fail-fast: false 45 | matrix: ${{ fromJson(needs.setup-matrix.outputs.matrix) }} 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@v4 49 | - name: Install pnpm 50 | uses: pnpm/action-setup@v4 51 | with: 52 | run_install: false 53 | - name: Install Node.js 54 | uses: actions/setup-node@v4 55 | with: 56 | node-version: lts/* 57 | cache: 'pnpm' 58 | - name: Install dependencies 59 | run: pnpm install --frozen-lockfile 60 | - name: Run Playwright tests 61 | run: xvfb-run pnpm exec playwright test ${{ github.event.inputs.test_command || '--grep-invert "@devRun"' }} --shard=${{ matrix.group }}/${{ github.event.inputs.parallelism || 2 }} 62 | - name: Upload test results and artifacts 63 | if: always() 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: allure-results-${{ matrix.group }} 67 | path: allure-results 68 | retention-days: 7 69 | 70 | merge-reports: 71 | needs: nightly-test 72 | if: always() 73 | runs-on: ubuntu-latest 74 | steps: 75 | - name: Merge and Publish Allure Report 76 | uses: Valiantsin2021/allure-shard-results-publish@1.0.6 77 | with: 78 | github-token: ${{ secrets.GITHUB_TOKEN }} 79 | add-env: 'true' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /blob-report/ 5 | /playwright/.cache/ 6 | *allure_results 7 | *allure-results 8 | *allure-report 9 | trace.zip 10 | # Environments 11 | .env 12 | .idea 13 | *.pem -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirtal85/Playwright-Typescript-Example/ed60af014714c8b1efec4ab34bcd9c386ffaaf51/README.md -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | We take the security of our project seriously. If you have discovered a security vulnerability, please follow these steps: 6 | 7 | 1. **Do not** disclose the vulnerability publicly. 8 | 2. Send a detailed vulnerability description to [nirt236@gmail.com](mailto:nirt236@gmail.com). 9 | 3. Expect a response within 48 hours. We will work with you to understand and address the issue. 10 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true 23 | } 24 | }, 25 | "javascript": { 26 | "formatter": { 27 | "quoteStyle": "double" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright-typescript", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "license": "ISC", 7 | "author": "Nir Tal", 8 | "type": "module", 9 | "scripts": { 10 | "biome:fix": "biome check --write --unsafe --no-errors-on-unmatched || true", 11 | "codegen": "playwright codegen", 12 | "install-browsers": "playwright install --with-deps", 13 | "prepare": "husky", 14 | "report:serve": "allure serve allure-results", 15 | "test": "playwright test", 16 | "test:debug": "PWDEBUG=1 playwright test --grep '@devRun'", 17 | "test:dev": "playwright test --grep '@devRun'", 18 | "test:non-dev": "playwright test --grep-invert '@devRun'" 19 | }, 20 | "lint-staged": { 21 | "*.{js,jsx,ts,tsx,json,md,yml}": ["npm run biome:fix --"] 22 | }, 23 | "devDependencies": { 24 | "@aws-sdk/client-s3": "3.787.0", 25 | "@aws-sdk/credential-provider-ini": "3.787.0", 26 | "@biomejs/biome": "1.9.4", 27 | "@playwright/test": "1.52.0", 28 | "@types/node": "22.15.3", 29 | "@types/ssh2-sftp-client": "^9.0.4", 30 | "@visual-regression-tracker/agent-playwright": "5.3.1", 31 | "allure-js-commons": "3.2.1", 32 | "allure-playwright": "3.2.1", 33 | "axios": "1.8.4", 34 | "dotenv": "16.5.0", 35 | "husky": "9.1.7", 36 | "lint-staged": "15.5.1", 37 | "mailinator-client": "1.0.5", 38 | "mysql2": "3.14.0", 39 | "ssh2-sftp-client": "12.0.0", 40 | "stripe": "18.1.0", 41 | "typescript": "5.8.3", 42 | "xlsx": "0.18.5", 43 | "zod": "3.24.3" 44 | }, 45 | "packageManager": "pnpm@10.10.0" 46 | } 47 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@playwright/test"; 2 | import { config as dotenvConfig } from "dotenv"; 3 | import { AUTOMATION_USER_AGENT } from "./src/utilities/constants"; 4 | 5 | dotenvConfig(); 6 | 7 | export default defineConfig({ 8 | testDir: "./tests", 9 | fullyParallel: true, 10 | forbidOnly: !!process.env.CI, 11 | retries: process.env.CI ? 2 : 0, 12 | workers: 1, 13 | timeout: 15 * 60 * 1000, 14 | expect: { timeout: 30 * 1000 }, 15 | reporter: [ 16 | [ 17 | "allure-playwright", 18 | { 19 | links: { 20 | issue: { nameTemplate: "Issue #%s", urlTemplate: "https://%s" }, 21 | tms: { nameTemplate: "TMS #%s", urlTemplate: "https://%s" }, 22 | link: { nameTemplate: "Link #%s", urlTemplate: "https://%s" }, 23 | }, 24 | }, 25 | ], 26 | ["junit", { outputFile: "test-results/results.xml" }], 27 | ], 28 | use: { 29 | screenshot: { 30 | mode: process.env.CI ? "only-on-failure" : "off", 31 | fullPage: true, 32 | }, 33 | trace: process.env.CI ? "retain-on-failure" : "off", 34 | video: process.env.CI ? "retain-on-failure" : "off", 35 | }, 36 | projects: [ 37 | { 38 | name: "chromium", 39 | use: { 40 | actionTimeout: 30 * 1000, 41 | navigationTimeout: 30 * 1000, 42 | viewport: null, 43 | testIdAttribute: "data-test", 44 | userAgent: AUTOMATION_USER_AGENT, 45 | permissions: [ 46 | "geolocation", 47 | "microphone", 48 | "camera", 49 | "clipboard-read", 50 | "clipboard-write", 51 | ], 52 | launchOptions: { 53 | headless: false, 54 | args: [ 55 | "--start-maximized", 56 | "--allow-file-access-from-files", 57 | "--use-fake-device-for-media-stream", 58 | "--use-fake-ui-for-media-stream", 59 | "--hide-scrollbars", 60 | "--disable-features=IsolateOrigins,site-per-process,VizDisplayCompositor,SidePanelPinning,OptimizationGuideModelDownloading,OptimizationHintsFetching,OptimizationTargetPrediction,OptimizationHints", 61 | "--disable-popup-blocking", 62 | "--disable-search-engine-choice-screen", 63 | "--disable-infobars", 64 | "--disable-dev-shm-usage", 65 | "--disable-notifications", 66 | "--disable-blink-features=AutomationControlled", 67 | ], 68 | }, 69 | }, 70 | }, 71 | ], 72 | }); 73 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@aws-sdk/client-s3': 12 | specifier: 3.787.0 13 | version: 3.787.0 14 | '@aws-sdk/credential-provider-ini': 15 | specifier: 3.787.0 16 | version: 3.787.0 17 | '@biomejs/biome': 18 | specifier: 1.9.4 19 | version: 1.9.4 20 | '@playwright/test': 21 | specifier: 1.52.0 22 | version: 1.52.0 23 | '@types/node': 24 | specifier: 22.15.3 25 | version: 22.15.3 26 | '@types/ssh2-sftp-client': 27 | specifier: ^9.0.4 28 | version: 9.0.4 29 | '@visual-regression-tracker/agent-playwright': 30 | specifier: 5.3.1 31 | version: 5.3.1 32 | allure-js-commons: 33 | specifier: 3.2.1 34 | version: 3.2.1(allure-playwright@3.2.1(@playwright/test@1.52.0)) 35 | allure-playwright: 36 | specifier: 3.2.1 37 | version: 3.2.1(@playwright/test@1.52.0) 38 | axios: 39 | specifier: 1.8.4 40 | version: 1.8.4 41 | dotenv: 42 | specifier: 16.5.0 43 | version: 16.5.0 44 | husky: 45 | specifier: 9.1.7 46 | version: 9.1.7 47 | lint-staged: 48 | specifier: 15.5.1 49 | version: 15.5.1 50 | mailinator-client: 51 | specifier: 1.0.5 52 | version: 1.0.5 53 | mysql2: 54 | specifier: 3.14.0 55 | version: 3.14.0 56 | ssh2-sftp-client: 57 | specifier: 12.0.0 58 | version: 12.0.0 59 | stripe: 60 | specifier: 18.1.0 61 | version: 18.1.0(@types/node@22.15.3) 62 | typescript: 63 | specifier: 5.8.3 64 | version: 5.8.3 65 | xlsx: 66 | specifier: 0.18.5 67 | version: 0.18.5 68 | zod: 69 | specifier: 3.24.3 70 | version: 3.24.3 71 | 72 | packages: 73 | 74 | '@aws-crypto/crc32@5.2.0': 75 | resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} 76 | engines: {node: '>=16.0.0'} 77 | 78 | '@aws-crypto/crc32c@5.2.0': 79 | resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} 80 | 81 | '@aws-crypto/sha1-browser@5.2.0': 82 | resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} 83 | 84 | '@aws-crypto/sha256-browser@5.2.0': 85 | resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} 86 | 87 | '@aws-crypto/sha256-js@5.2.0': 88 | resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} 89 | engines: {node: '>=16.0.0'} 90 | 91 | '@aws-crypto/supports-web-crypto@5.2.0': 92 | resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} 93 | 94 | '@aws-crypto/util@5.2.0': 95 | resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} 96 | 97 | '@aws-sdk/client-s3@3.787.0': 98 | resolution: {integrity: sha512-eGLCWkN0NlntJ9yPU6OKUggVS4cFvuZJog+cFg1KD5hniLqz7Y0YRtB4uBxW212fK3XCfddgyscEOEeHaTQQTw==} 99 | engines: {node: '>=18.0.0'} 100 | 101 | '@aws-sdk/client-sso@3.787.0': 102 | resolution: {integrity: sha512-L8R+Mh258G0DC73ktpSVrG4TT9i2vmDLecARTDR/4q5sRivdDQSL5bUp3LKcK80Bx+FRw3UETIlX6mYMLL9PJQ==} 103 | engines: {node: '>=18.0.0'} 104 | 105 | '@aws-sdk/core@3.775.0': 106 | resolution: {integrity: sha512-8vpW4WihVfz0DX+7WnnLGm3GuQER++b0IwQG35JlQMlgqnc44M//KbJPsIHA0aJUJVwJAEShgfr5dUbY8WUzaA==} 107 | engines: {node: '>=18.0.0'} 108 | 109 | '@aws-sdk/credential-provider-env@3.775.0': 110 | resolution: {integrity: sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw==} 111 | engines: {node: '>=18.0.0'} 112 | 113 | '@aws-sdk/credential-provider-http@3.775.0': 114 | resolution: {integrity: sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww==} 115 | engines: {node: '>=18.0.0'} 116 | 117 | '@aws-sdk/credential-provider-ini@3.787.0': 118 | resolution: {integrity: sha512-hc2taRoDlXn2uuNuHWDJljVWYrp3r9JF1a/8XmOAZhVUNY+ImeeStylHXhXXKEA4JOjW+5PdJj0f1UDkVCHJiQ==} 119 | engines: {node: '>=18.0.0'} 120 | 121 | '@aws-sdk/credential-provider-node@3.787.0': 122 | resolution: {integrity: sha512-JioVi44B1vDMaK2CdzqimwvJD3uzvzbQhaEWXsGMBcMcNHajXAXf08EF50JG3ZhLrhhUsT1ObXpbTaPINOhh+g==} 123 | engines: {node: '>=18.0.0'} 124 | 125 | '@aws-sdk/credential-provider-process@3.775.0': 126 | resolution: {integrity: sha512-A6k68H9rQp+2+7P7SGO90Csw6nrUEm0Qfjpn9Etc4EboZhhCLs9b66umUsTsSBHus4FDIe5JQxfCUyt1wgNogg==} 127 | engines: {node: '>=18.0.0'} 128 | 129 | '@aws-sdk/credential-provider-sso@3.787.0': 130 | resolution: {integrity: sha512-fHc08bsvwm4+dEMEQKnQ7c1irEQmmxbgS+Fq41y09pPvPh31nAhoMcjBSTWAaPHvvsRbTYvmP4Mf12ZGr8/nfg==} 131 | engines: {node: '>=18.0.0'} 132 | 133 | '@aws-sdk/credential-provider-web-identity@3.787.0': 134 | resolution: {integrity: sha512-SobmCwNbk6TfEsF283mZPQEI5vV2j6eY5tOCj8Er4Lzraxu9fBPADV+Bib2A8F6jlB1lMPJzOuDCbEasSt/RIw==} 135 | engines: {node: '>=18.0.0'} 136 | 137 | '@aws-sdk/middleware-bucket-endpoint@3.775.0': 138 | resolution: {integrity: sha512-qogMIpVChDYr4xiUNC19/RDSw/sKoHkAhouS6Skxiy6s27HBhow1L3Z1qVYXuBmOZGSWPU0xiyZCvOyWrv9s+Q==} 139 | engines: {node: '>=18.0.0'} 140 | 141 | '@aws-sdk/middleware-expect-continue@3.775.0': 142 | resolution: {integrity: sha512-Apd3owkIeUW5dnk3au9np2IdW2N0zc9NjTjHiH+Mx3zqwSrc+m+ANgJVgk9mnQjMzU/vb7VuxJ0eqdEbp5gYsg==} 143 | engines: {node: '>=18.0.0'} 144 | 145 | '@aws-sdk/middleware-flexible-checksums@3.787.0': 146 | resolution: {integrity: sha512-X71qEwWoixFmwowWzlPoZUR3u1CWJ7iAzU0EzIxqmPhQpQJLFmdL1+SRjqATynDPZQzLs1a5HBtPT++EnZ+Quw==} 147 | engines: {node: '>=18.0.0'} 148 | 149 | '@aws-sdk/middleware-host-header@3.775.0': 150 | resolution: {integrity: sha512-tkSegM0Z6WMXpLB8oPys/d+umYIocvO298mGvcMCncpRl77L9XkvSLJIFzaHes+o7djAgIduYw8wKIMStFss2w==} 151 | engines: {node: '>=18.0.0'} 152 | 153 | '@aws-sdk/middleware-location-constraint@3.775.0': 154 | resolution: {integrity: sha512-8TMXEHZXZTFTckQLyBT5aEI8fX11HZcwZseRifvBKKpj0RZDk4F0EEYGxeNSPpUQ7n+PRWyfAEnnZNRdAj/1NQ==} 155 | engines: {node: '>=18.0.0'} 156 | 157 | '@aws-sdk/middleware-logger@3.775.0': 158 | resolution: {integrity: sha512-FaxO1xom4MAoUJsldmR92nT1G6uZxTdNYOFYtdHfd6N2wcNaTuxgjIvqzg5y7QIH9kn58XX/dzf1iTjgqUStZw==} 159 | engines: {node: '>=18.0.0'} 160 | 161 | '@aws-sdk/middleware-recursion-detection@3.775.0': 162 | resolution: {integrity: sha512-GLCzC8D0A0YDG5u3F5U03Vb9j5tcOEFhr8oc6PDk0k0vm5VwtZOE6LvK7hcCSoAB4HXyOUM0sQuXrbaAh9OwXA==} 163 | engines: {node: '>=18.0.0'} 164 | 165 | '@aws-sdk/middleware-sdk-s3@3.775.0': 166 | resolution: {integrity: sha512-zsvcu7cWB28JJ60gVvjxPCI7ZU7jWGcpNACPiZGyVtjYXwcxyhXbYEVDSWKsSA6ERpz9XrpLYod8INQWfW3ECg==} 167 | engines: {node: '>=18.0.0'} 168 | 169 | '@aws-sdk/middleware-ssec@3.775.0': 170 | resolution: {integrity: sha512-Iw1RHD8vfAWWPzBBIKaojO4GAvQkHOYIpKdAfis/EUSUmSa79QsnXnRqsdcE0mCB0Ylj23yi+ah4/0wh9FsekA==} 171 | engines: {node: '>=18.0.0'} 172 | 173 | '@aws-sdk/middleware-user-agent@3.787.0': 174 | resolution: {integrity: sha512-Lnfj8SmPLYtrDFthNIaNj66zZsBCam+E4XiUDr55DIHTGstH6qZ/q6vg0GfbukxwSmUcGMwSR4Qbn8rb8yd77g==} 175 | engines: {node: '>=18.0.0'} 176 | 177 | '@aws-sdk/nested-clients@3.787.0': 178 | resolution: {integrity: sha512-xk03q1xpKNHgbuo+trEf1dFrI239kuMmjKKsqLEsHlAZbuFq4yRGMlHBrVMnKYOPBhVFDS/VineM991XI52fKg==} 179 | engines: {node: '>=18.0.0'} 180 | 181 | '@aws-sdk/region-config-resolver@3.775.0': 182 | resolution: {integrity: sha512-40iH3LJjrQS3LKUJAl7Wj0bln7RFPEvUYKFxtP8a+oKFDO0F65F52xZxIJbPn6sHkxWDAnZlGgdjZXM3p2g5wQ==} 183 | engines: {node: '>=18.0.0'} 184 | 185 | '@aws-sdk/signature-v4-multi-region@3.775.0': 186 | resolution: {integrity: sha512-cnGk8GDfTMJ8p7+qSk92QlIk2bmTmFJqhYxcXZ9PysjZtx0xmfCMxnG3Hjy1oU2mt5boPCVSOptqtWixayM17g==} 187 | engines: {node: '>=18.0.0'} 188 | 189 | '@aws-sdk/token-providers@3.787.0': 190 | resolution: {integrity: sha512-d7/NIqxq308Zg0RPMNrmn0QvzniL4Hx8Qdwzr6YZWLYAbUSvZYS2ppLR3BFWSkV6SsTJUx8BuDaj3P8vttkrog==} 191 | engines: {node: '>=18.0.0'} 192 | 193 | '@aws-sdk/types@3.775.0': 194 | resolution: {integrity: sha512-ZoGKwa4C9fC9Av6bdfqcW6Ix5ot05F/S4VxWR2nHuMv7hzfmAjTOcUiWT7UR4hM/U0whf84VhDtXN/DWAk52KA==} 195 | engines: {node: '>=18.0.0'} 196 | 197 | '@aws-sdk/util-arn-parser@3.723.0': 198 | resolution: {integrity: sha512-ZhEfvUwNliOQROcAk34WJWVYTlTa4694kSVhDSjW6lE1bMataPnIN8A0ycukEzBXmd8ZSoBcQLn6lKGl7XIJ5w==} 199 | engines: {node: '>=18.0.0'} 200 | 201 | '@aws-sdk/util-endpoints@3.787.0': 202 | resolution: {integrity: sha512-fd3zkiOkwnbdbN0Xp9TsP5SWrmv0SpT70YEdbb8wAj2DWQwiCmFszaSs+YCvhoCdmlR3Wl9Spu0pGpSAGKeYvQ==} 203 | engines: {node: '>=18.0.0'} 204 | 205 | '@aws-sdk/util-locate-window@3.723.0': 206 | resolution: {integrity: sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==} 207 | engines: {node: '>=18.0.0'} 208 | 209 | '@aws-sdk/util-user-agent-browser@3.775.0': 210 | resolution: {integrity: sha512-txw2wkiJmZKVdDbscK7VBK+u+TJnRtlUjRTLei+elZg2ADhpQxfVAQl436FUeIv6AhB/oRHW6/K/EAGXUSWi0A==} 211 | 212 | '@aws-sdk/util-user-agent-node@3.787.0': 213 | resolution: {integrity: sha512-mG7Lz8ydfG4SF9e8WSXiPQ/Lsn3n8A5B5jtPROidafi06I3ckV2WxyMLdwG14m919NoS6IOfWHyRGSqWIwbVKA==} 214 | engines: {node: '>=18.0.0'} 215 | peerDependencies: 216 | aws-crt: '>=1.0.0' 217 | peerDependenciesMeta: 218 | aws-crt: 219 | optional: true 220 | 221 | '@aws-sdk/xml-builder@3.775.0': 222 | resolution: {integrity: sha512-b9NGO6FKJeLGYnV7Z1yvcP1TNU4dkD5jNsLWOF1/sygZoASaQhNOlaiJ/1OH331YQ1R1oWk38nBb0frsYkDsOQ==} 223 | engines: {node: '>=18.0.0'} 224 | 225 | '@biomejs/biome@1.9.4': 226 | resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} 227 | engines: {node: '>=14.21.3'} 228 | hasBin: true 229 | 230 | '@biomejs/cli-darwin-arm64@1.9.4': 231 | resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} 232 | engines: {node: '>=14.21.3'} 233 | cpu: [arm64] 234 | os: [darwin] 235 | 236 | '@biomejs/cli-darwin-x64@1.9.4': 237 | resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} 238 | engines: {node: '>=14.21.3'} 239 | cpu: [x64] 240 | os: [darwin] 241 | 242 | '@biomejs/cli-linux-arm64-musl@1.9.4': 243 | resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} 244 | engines: {node: '>=14.21.3'} 245 | cpu: [arm64] 246 | os: [linux] 247 | 248 | '@biomejs/cli-linux-arm64@1.9.4': 249 | resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} 250 | engines: {node: '>=14.21.3'} 251 | cpu: [arm64] 252 | os: [linux] 253 | 254 | '@biomejs/cli-linux-x64-musl@1.9.4': 255 | resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} 256 | engines: {node: '>=14.21.3'} 257 | cpu: [x64] 258 | os: [linux] 259 | 260 | '@biomejs/cli-linux-x64@1.9.4': 261 | resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} 262 | engines: {node: '>=14.21.3'} 263 | cpu: [x64] 264 | os: [linux] 265 | 266 | '@biomejs/cli-win32-arm64@1.9.4': 267 | resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} 268 | engines: {node: '>=14.21.3'} 269 | cpu: [arm64] 270 | os: [win32] 271 | 272 | '@biomejs/cli-win32-x64@1.9.4': 273 | resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} 274 | engines: {node: '>=14.21.3'} 275 | cpu: [x64] 276 | os: [win32] 277 | 278 | '@playwright/test@1.52.0': 279 | resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==} 280 | engines: {node: '>=18'} 281 | hasBin: true 282 | 283 | '@smithy/abort-controller@4.0.2': 284 | resolution: {integrity: sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==} 285 | engines: {node: '>=18.0.0'} 286 | 287 | '@smithy/chunked-blob-reader-native@4.0.0': 288 | resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==} 289 | engines: {node: '>=18.0.0'} 290 | 291 | '@smithy/chunked-blob-reader@5.0.0': 292 | resolution: {integrity: sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==} 293 | engines: {node: '>=18.0.0'} 294 | 295 | '@smithy/config-resolver@4.1.0': 296 | resolution: {integrity: sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==} 297 | engines: {node: '>=18.0.0'} 298 | 299 | '@smithy/core@3.3.0': 300 | resolution: {integrity: sha512-r6gvs5OfRq/w+9unPm7B3po4rmWaGh0CIL/OwHntGGux7+RhOOZLGuurbeMgWV6W55ZuyMTypJLeH0vn/ZRaWQ==} 301 | engines: {node: '>=18.0.0'} 302 | 303 | '@smithy/credential-provider-imds@4.0.2': 304 | resolution: {integrity: sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==} 305 | engines: {node: '>=18.0.0'} 306 | 307 | '@smithy/eventstream-codec@4.0.2': 308 | resolution: {integrity: sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==} 309 | engines: {node: '>=18.0.0'} 310 | 311 | '@smithy/eventstream-serde-browser@4.0.2': 312 | resolution: {integrity: sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==} 313 | engines: {node: '>=18.0.0'} 314 | 315 | '@smithy/eventstream-serde-config-resolver@4.1.0': 316 | resolution: {integrity: sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==} 317 | engines: {node: '>=18.0.0'} 318 | 319 | '@smithy/eventstream-serde-node@4.0.2': 320 | resolution: {integrity: sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==} 321 | engines: {node: '>=18.0.0'} 322 | 323 | '@smithy/eventstream-serde-universal@4.0.2': 324 | resolution: {integrity: sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==} 325 | engines: {node: '>=18.0.0'} 326 | 327 | '@smithy/fetch-http-handler@5.0.2': 328 | resolution: {integrity: sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==} 329 | engines: {node: '>=18.0.0'} 330 | 331 | '@smithy/hash-blob-browser@4.0.2': 332 | resolution: {integrity: sha512-3g188Z3DyhtzfBRxpZjU8R9PpOQuYsbNnyStc/ZVS+9nVX1f6XeNOa9IrAh35HwwIZg+XWk8bFVtNINVscBP+g==} 333 | engines: {node: '>=18.0.0'} 334 | 335 | '@smithy/hash-node@4.0.2': 336 | resolution: {integrity: sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==} 337 | engines: {node: '>=18.0.0'} 338 | 339 | '@smithy/hash-stream-node@4.0.2': 340 | resolution: {integrity: sha512-POWDuTznzbIwlEXEvvXoPMS10y0WKXK790soe57tFRfvf4zBHyzE529HpZMqmDdwG9MfFflnyzndUQ8j78ZdSg==} 341 | engines: {node: '>=18.0.0'} 342 | 343 | '@smithy/invalid-dependency@4.0.2': 344 | resolution: {integrity: sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==} 345 | engines: {node: '>=18.0.0'} 346 | 347 | '@smithy/is-array-buffer@2.2.0': 348 | resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} 349 | engines: {node: '>=14.0.0'} 350 | 351 | '@smithy/is-array-buffer@4.0.0': 352 | resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==} 353 | engines: {node: '>=18.0.0'} 354 | 355 | '@smithy/md5-js@4.0.2': 356 | resolution: {integrity: sha512-Hc0R8EiuVunUewCse2syVgA2AfSRco3LyAv07B/zCOMa+jpXI9ll+Q21Nc6FAlYPcpNcAXqBzMhNs1CD/pP2bA==} 357 | engines: {node: '>=18.0.0'} 358 | 359 | '@smithy/middleware-content-length@4.0.2': 360 | resolution: {integrity: sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==} 361 | engines: {node: '>=18.0.0'} 362 | 363 | '@smithy/middleware-endpoint@4.1.1': 364 | resolution: {integrity: sha512-z5RmcHxjvScL+LwEDU2mTNCOhgUs4lu5PGdF1K36IPRmUHhNFxNxgenSB7smyDiYD4vdKQ7CAZtG5cUErqib9w==} 365 | engines: {node: '>=18.0.0'} 366 | 367 | '@smithy/middleware-retry@4.1.2': 368 | resolution: {integrity: sha512-qN/Mmxm8JWtFAjozJ8VSTM83KOX4cIks8UjDqqNkKIegzPrE5ZKPNCQ/DqUSIF90pue5a/NycNXnBod2NwvZZQ==} 369 | engines: {node: '>=18.0.0'} 370 | 371 | '@smithy/middleware-serde@4.0.3': 372 | resolution: {integrity: sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==} 373 | engines: {node: '>=18.0.0'} 374 | 375 | '@smithy/middleware-stack@4.0.2': 376 | resolution: {integrity: sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==} 377 | engines: {node: '>=18.0.0'} 378 | 379 | '@smithy/node-config-provider@4.0.2': 380 | resolution: {integrity: sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==} 381 | engines: {node: '>=18.0.0'} 382 | 383 | '@smithy/node-http-handler@4.0.4': 384 | resolution: {integrity: sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==} 385 | engines: {node: '>=18.0.0'} 386 | 387 | '@smithy/property-provider@4.0.2': 388 | resolution: {integrity: sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==} 389 | engines: {node: '>=18.0.0'} 390 | 391 | '@smithy/protocol-http@5.1.0': 392 | resolution: {integrity: sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==} 393 | engines: {node: '>=18.0.0'} 394 | 395 | '@smithy/querystring-builder@4.0.2': 396 | resolution: {integrity: sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==} 397 | engines: {node: '>=18.0.0'} 398 | 399 | '@smithy/querystring-parser@4.0.2': 400 | resolution: {integrity: sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==} 401 | engines: {node: '>=18.0.0'} 402 | 403 | '@smithy/service-error-classification@4.0.3': 404 | resolution: {integrity: sha512-FTbcajmltovWMjj3tksDQdD23b2w6gH+A0DYA1Yz3iSpjDj8fmkwy62UnXcWMy4d5YoMoSyLFHMfkEVEzbiN8Q==} 405 | engines: {node: '>=18.0.0'} 406 | 407 | '@smithy/shared-ini-file-loader@4.0.2': 408 | resolution: {integrity: sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==} 409 | engines: {node: '>=18.0.0'} 410 | 411 | '@smithy/signature-v4@5.1.0': 412 | resolution: {integrity: sha512-4t5WX60sL3zGJF/CtZsUQTs3UrZEDO2P7pEaElrekbLqkWPYkgqNW1oeiNYC6xXifBnT9dVBOnNQRvOE9riU9w==} 413 | engines: {node: '>=18.0.0'} 414 | 415 | '@smithy/smithy-client@4.2.1': 416 | resolution: {integrity: sha512-fbniZef60QdsBc4ZY0iyI8xbFHIiC/QRtPi66iE4ufjiE/aaz7AfUXzcWMkpO8r+QhLeNRIfmPchIG+3/QDZ6g==} 417 | engines: {node: '>=18.0.0'} 418 | 419 | '@smithy/types@4.2.0': 420 | resolution: {integrity: sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==} 421 | engines: {node: '>=18.0.0'} 422 | 423 | '@smithy/url-parser@4.0.2': 424 | resolution: {integrity: sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==} 425 | engines: {node: '>=18.0.0'} 426 | 427 | '@smithy/util-base64@4.0.0': 428 | resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==} 429 | engines: {node: '>=18.0.0'} 430 | 431 | '@smithy/util-body-length-browser@4.0.0': 432 | resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==} 433 | engines: {node: '>=18.0.0'} 434 | 435 | '@smithy/util-body-length-node@4.0.0': 436 | resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==} 437 | engines: {node: '>=18.0.0'} 438 | 439 | '@smithy/util-buffer-from@2.2.0': 440 | resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} 441 | engines: {node: '>=14.0.0'} 442 | 443 | '@smithy/util-buffer-from@4.0.0': 444 | resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==} 445 | engines: {node: '>=18.0.0'} 446 | 447 | '@smithy/util-config-provider@4.0.0': 448 | resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==} 449 | engines: {node: '>=18.0.0'} 450 | 451 | '@smithy/util-defaults-mode-browser@4.0.9': 452 | resolution: {integrity: sha512-B8j0XsElvyhv6+5hlFf6vFV/uCSyLKcInpeXOGnOImX2mGXshE01RvPoGipTlRpIk53e6UfYj7WdDdgbVfXDZw==} 453 | engines: {node: '>=18.0.0'} 454 | 455 | '@smithy/util-defaults-mode-node@4.0.9': 456 | resolution: {integrity: sha512-wTDU8P/zdIf9DOpV5qm64HVgGRXvqjqB/fJZTEQbrz3s79JHM/E7XkMm/876Oq+ZLHJQgnXM9QHDo29dlM62eA==} 457 | engines: {node: '>=18.0.0'} 458 | 459 | '@smithy/util-endpoints@3.0.2': 460 | resolution: {integrity: sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==} 461 | engines: {node: '>=18.0.0'} 462 | 463 | '@smithy/util-hex-encoding@4.0.0': 464 | resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==} 465 | engines: {node: '>=18.0.0'} 466 | 467 | '@smithy/util-middleware@4.0.2': 468 | resolution: {integrity: sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==} 469 | engines: {node: '>=18.0.0'} 470 | 471 | '@smithy/util-retry@4.0.3': 472 | resolution: {integrity: sha512-DPuYjZQDXmKr/sNvy9Spu8R/ESa2e22wXZzSAY6NkjOLj6spbIje/Aq8rT97iUMdDj0qHMRIe+bTxvlU74d9Ng==} 473 | engines: {node: '>=18.0.0'} 474 | 475 | '@smithy/util-stream@4.2.0': 476 | resolution: {integrity: sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==} 477 | engines: {node: '>=18.0.0'} 478 | 479 | '@smithy/util-uri-escape@4.0.0': 480 | resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==} 481 | engines: {node: '>=18.0.0'} 482 | 483 | '@smithy/util-utf8@2.3.0': 484 | resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} 485 | engines: {node: '>=14.0.0'} 486 | 487 | '@smithy/util-utf8@4.0.0': 488 | resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==} 489 | engines: {node: '>=18.0.0'} 490 | 491 | '@smithy/util-waiter@4.0.3': 492 | resolution: {integrity: sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==} 493 | engines: {node: '>=18.0.0'} 494 | 495 | '@types/node@13.13.52': 496 | resolution: {integrity: sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==} 497 | 498 | '@types/node@18.19.86': 499 | resolution: {integrity: sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==} 500 | 501 | '@types/node@22.15.3': 502 | resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==} 503 | 504 | '@types/ssh2-sftp-client@9.0.4': 505 | resolution: {integrity: sha512-gnIn56MTB9W3A3hPL/1sHI23t8YwcE3eVYa1O2XjT9vaqimFdtNHxyQiy5Y78+ociQTKazMSD8YyMEO4QjNMrg==} 506 | 507 | '@types/ssh2@1.15.5': 508 | resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} 509 | 510 | '@visual-regression-tracker/agent-playwright@5.3.1': 511 | resolution: {integrity: sha512-sy9sT86Tp8QfXMzojgZ9V3MxoFMZttVSrPmV78Gl0Hun3mF50uUhtXwV/64SwdJcVUnaostyIaombIluuhVH0Q==} 512 | engines: {node: '>=18'} 513 | 514 | '@visual-regression-tracker/sdk-js@5.7.1': 515 | resolution: {integrity: sha512-6EnyTX5Fd6uhcWIUwsVtPmPdlpr5D/s9m0Yxo0Hk/yI+/y0uSil34ulVNKXjaKGin7sZDzoj5CQe8ysl+Z/XfQ==} 516 | engines: {node: '>=18'} 517 | 518 | adler-32@1.3.1: 519 | resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==} 520 | engines: {node: '>=0.8'} 521 | 522 | allure-js-commons@3.2.1: 523 | resolution: {integrity: sha512-VGLv6u3I9NIm649dYmGpG3X5sOnI0tt7K1WYTVZPiEpnsxBbFEfDr6yAI7q509WWw/5g0VBCEtGEspd6AoYOqA==} 524 | peerDependencies: 525 | allure-playwright: 3.2.1 526 | peerDependenciesMeta: 527 | allure-playwright: 528 | optional: true 529 | 530 | allure-playwright@3.2.1: 531 | resolution: {integrity: sha512-IO0lDDz007hlaHH8hZzrvyaw0NR6ZOGBEPI/ANUGbBizFOXcDf9J/nHDCdsNrt/Qa13H4AGGU7sDjtng1evYgA==} 532 | peerDependencies: 533 | '@playwright/test': '>=1.36.0' 534 | 535 | ansi-escapes@7.0.0: 536 | resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} 537 | engines: {node: '>=18'} 538 | 539 | ansi-regex@5.0.1: 540 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 541 | engines: {node: '>=8'} 542 | 543 | ansi-regex@6.0.1: 544 | resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} 545 | engines: {node: '>=12'} 546 | 547 | ansi-styles@4.3.0: 548 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 549 | engines: {node: '>=8'} 550 | 551 | ansi-styles@6.2.1: 552 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 553 | engines: {node: '>=12'} 554 | 555 | asn1@0.2.6: 556 | resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} 557 | 558 | asynckit@0.4.0: 559 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 560 | 561 | aws-ssl-profiles@1.1.2: 562 | resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} 563 | engines: {node: '>= 6.0.0'} 564 | 565 | axios@1.8.4: 566 | resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} 567 | 568 | balanced-match@1.0.2: 569 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 570 | 571 | bcrypt-pbkdf@1.0.2: 572 | resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} 573 | 574 | bowser@2.11.0: 575 | resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} 576 | 577 | brace-expansion@1.1.11: 578 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 579 | 580 | braces@3.0.3: 581 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 582 | engines: {node: '>=8'} 583 | 584 | buffer-from@1.1.2: 585 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 586 | 587 | buildcheck@0.0.6: 588 | resolution: {integrity: sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==} 589 | engines: {node: '>=10.0.0'} 590 | 591 | call-bind-apply-helpers@1.0.2: 592 | resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 593 | engines: {node: '>= 0.4'} 594 | 595 | call-bound@1.0.4: 596 | resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 597 | engines: {node: '>= 0.4'} 598 | 599 | camelcase@5.3.1: 600 | resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} 601 | engines: {node: '>=6'} 602 | 603 | cfb@1.2.2: 604 | resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==} 605 | engines: {node: '>=0.8'} 606 | 607 | chalk@4.1.2: 608 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 609 | engines: {node: '>=10'} 610 | 611 | chalk@5.4.1: 612 | resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} 613 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 614 | 615 | charenc@0.0.2: 616 | resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} 617 | 618 | cli-cursor@5.0.0: 619 | resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} 620 | engines: {node: '>=18'} 621 | 622 | cli-truncate@4.0.0: 623 | resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} 624 | engines: {node: '>=18'} 625 | 626 | cliui@6.0.0: 627 | resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} 628 | 629 | codepage@1.15.0: 630 | resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==} 631 | engines: {node: '>=0.8'} 632 | 633 | color-convert@2.0.1: 634 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 635 | engines: {node: '>=7.0.0'} 636 | 637 | color-name@1.1.4: 638 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 639 | 640 | colorette@2.0.20: 641 | resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} 642 | 643 | combined-stream@1.0.8: 644 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 645 | engines: {node: '>= 0.8'} 646 | 647 | commander@13.1.0: 648 | resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} 649 | engines: {node: '>=18'} 650 | 651 | concat-map@0.0.1: 652 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 653 | 654 | concat-stream@2.0.0: 655 | resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} 656 | engines: {'0': node >= 6.0} 657 | 658 | cpu-features@0.0.10: 659 | resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} 660 | engines: {node: '>=10.0.0'} 661 | 662 | crc-32@1.2.2: 663 | resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} 664 | engines: {node: '>=0.8'} 665 | hasBin: true 666 | 667 | create-index@2.6.0: 668 | resolution: {integrity: sha512-c9p7sNMIfiKv+RRORw9jbwxMRLrkt0Hq8PFGLxcDXGoMqqGrwnDIw97tUnpdp9rAkIToAPbqejH6qHpGWQGAww==} 669 | engines: {node: '>=5'} 670 | hasBin: true 671 | 672 | cross-spawn@7.0.6: 673 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 674 | engines: {node: '>= 8'} 675 | 676 | crypt@0.0.2: 677 | resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} 678 | 679 | debug@4.4.0: 680 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 681 | engines: {node: '>=6.0'} 682 | peerDependencies: 683 | supports-color: '*' 684 | peerDependenciesMeta: 685 | supports-color: 686 | optional: true 687 | 688 | decamelize@1.2.0: 689 | resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} 690 | engines: {node: '>=0.10.0'} 691 | 692 | delayed-stream@1.0.0: 693 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 694 | engines: {node: '>=0.4.0'} 695 | 696 | denque@2.1.0: 697 | resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} 698 | engines: {node: '>=0.10'} 699 | 700 | dotenv@16.5.0: 701 | resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} 702 | engines: {node: '>=12'} 703 | 704 | dunder-proto@1.0.1: 705 | resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 706 | engines: {node: '>= 0.4'} 707 | 708 | emoji-regex@10.3.0: 709 | resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} 710 | 711 | emoji-regex@8.0.0: 712 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 713 | 714 | environment@1.1.0: 715 | resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} 716 | engines: {node: '>=18'} 717 | 718 | es-define-property@1.0.1: 719 | resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 720 | engines: {node: '>= 0.4'} 721 | 722 | es-errors@1.3.0: 723 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 724 | engines: {node: '>= 0.4'} 725 | 726 | es-object-atoms@1.1.1: 727 | resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 728 | engines: {node: '>= 0.4'} 729 | 730 | es-set-tostringtag@2.1.0: 731 | resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} 732 | engines: {node: '>= 0.4'} 733 | 734 | eventemitter3@5.0.1: 735 | resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} 736 | 737 | execa@8.0.1: 738 | resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} 739 | engines: {node: '>=16.17'} 740 | 741 | fast-xml-parser@4.4.1: 742 | resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} 743 | hasBin: true 744 | 745 | fill-range@7.1.1: 746 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 747 | engines: {node: '>=8'} 748 | 749 | find-up@4.1.0: 750 | resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} 751 | engines: {node: '>=8'} 752 | 753 | follow-redirects@1.15.9: 754 | resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} 755 | engines: {node: '>=4.0'} 756 | peerDependencies: 757 | debug: '*' 758 | peerDependenciesMeta: 759 | debug: 760 | optional: true 761 | 762 | form-data@4.0.2: 763 | resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} 764 | engines: {node: '>= 6'} 765 | 766 | frac@1.1.2: 767 | resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} 768 | engines: {node: '>=0.8'} 769 | 770 | fs.realpath@1.0.0: 771 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 772 | 773 | fs@0.0.1-security: 774 | resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} 775 | 776 | fsevents@2.3.2: 777 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 778 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 779 | os: [darwin] 780 | 781 | function-bind@1.1.2: 782 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 783 | 784 | generate-function@2.3.1: 785 | resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} 786 | 787 | get-caller-file@2.0.5: 788 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 789 | engines: {node: 6.* || 8.* || >= 10.*} 790 | 791 | get-east-asian-width@1.2.0: 792 | resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} 793 | engines: {node: '>=18'} 794 | 795 | get-intrinsic@1.3.0: 796 | resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 797 | engines: {node: '>= 0.4'} 798 | 799 | get-proto@1.0.1: 800 | resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 801 | engines: {node: '>= 0.4'} 802 | 803 | get-stream@8.0.1: 804 | resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} 805 | engines: {node: '>=16'} 806 | 807 | glob@7.2.3: 808 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 809 | deprecated: Glob versions prior to v9 are no longer supported 810 | 811 | gopd@1.2.0: 812 | resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 813 | engines: {node: '>= 0.4'} 814 | 815 | has-flag@4.0.0: 816 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 817 | engines: {node: '>=8'} 818 | 819 | has-symbols@1.1.0: 820 | resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 821 | engines: {node: '>= 0.4'} 822 | 823 | has-tostringtag@1.0.2: 824 | resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 825 | engines: {node: '>= 0.4'} 826 | 827 | hasown@2.0.2: 828 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 829 | engines: {node: '>= 0.4'} 830 | 831 | human-signals@5.0.0: 832 | resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} 833 | engines: {node: '>=16.17.0'} 834 | 835 | husky@9.1.7: 836 | resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} 837 | engines: {node: '>=18'} 838 | hasBin: true 839 | 840 | iconv-lite@0.6.3: 841 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} 842 | engines: {node: '>=0.10.0'} 843 | 844 | inflight@1.0.6: 845 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 846 | deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. 847 | 848 | inherits@2.0.4: 849 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 850 | 851 | is-buffer@1.1.6: 852 | resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} 853 | 854 | is-fullwidth-code-point@3.0.0: 855 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 856 | engines: {node: '>=8'} 857 | 858 | is-fullwidth-code-point@4.0.0: 859 | resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} 860 | engines: {node: '>=12'} 861 | 862 | is-fullwidth-code-point@5.0.0: 863 | resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} 864 | engines: {node: '>=18'} 865 | 866 | is-number@7.0.0: 867 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 868 | engines: {node: '>=0.12.0'} 869 | 870 | is-property@1.0.2: 871 | resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} 872 | 873 | is-stream@3.0.0: 874 | resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} 875 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 876 | 877 | isexe@2.0.0: 878 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 879 | 880 | lilconfig@3.1.3: 881 | resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 882 | engines: {node: '>=14'} 883 | 884 | lint-staged@15.5.1: 885 | resolution: {integrity: sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==} 886 | engines: {node: '>=18.12.0'} 887 | hasBin: true 888 | 889 | listr2@8.2.5: 890 | resolution: {integrity: sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==} 891 | engines: {node: '>=18.0.0'} 892 | 893 | locate-path@5.0.0: 894 | resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} 895 | engines: {node: '>=8'} 896 | 897 | lodash@4.17.21: 898 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 899 | 900 | log-update@6.1.0: 901 | resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} 902 | engines: {node: '>=18'} 903 | 904 | long@5.3.1: 905 | resolution: {integrity: sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==} 906 | 907 | lru-cache@7.18.3: 908 | resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} 909 | engines: {node: '>=12'} 910 | 911 | lru.min@1.1.2: 912 | resolution: {integrity: sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==} 913 | engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} 914 | 915 | mailinator-client@1.0.5: 916 | resolution: {integrity: sha512-SWcKBhWuiyGG6IKNDptPs2wWZrjkkp94DteMKHBmow4+Ndvv+05DNm1EN9+Yq2yNInxhfLFHW09yCb5cEeQggQ==} 917 | 918 | math-intrinsics@1.1.0: 919 | resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 920 | engines: {node: '>= 0.4'} 921 | 922 | md5@2.3.0: 923 | resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} 924 | 925 | merge-stream@2.0.0: 926 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 927 | 928 | micromatch@4.0.8: 929 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 930 | engines: {node: '>=8.6'} 931 | 932 | mime-db@1.52.0: 933 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 934 | engines: {node: '>= 0.6'} 935 | 936 | mime-types@2.1.35: 937 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 938 | engines: {node: '>= 0.6'} 939 | 940 | mimic-fn@4.0.0: 941 | resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} 942 | engines: {node: '>=12'} 943 | 944 | mimic-function@5.0.1: 945 | resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} 946 | engines: {node: '>=18'} 947 | 948 | minimatch@3.1.2: 949 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 950 | 951 | moment@2.30.1: 952 | resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} 953 | 954 | ms@2.1.3: 955 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 956 | 957 | mysql2@3.14.0: 958 | resolution: {integrity: sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==} 959 | engines: {node: '>= 8.0'} 960 | 961 | named-placeholders@1.1.3: 962 | resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} 963 | engines: {node: '>=12.0.0'} 964 | 965 | nan@2.22.2: 966 | resolution: {integrity: sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==} 967 | 968 | npm-run-path@5.3.0: 969 | resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} 970 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 971 | 972 | object-inspect@1.13.4: 973 | resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 974 | engines: {node: '>= 0.4'} 975 | 976 | once@1.4.0: 977 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 978 | 979 | onetime@6.0.0: 980 | resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} 981 | engines: {node: '>=12'} 982 | 983 | onetime@7.0.0: 984 | resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} 985 | engines: {node: '>=18'} 986 | 987 | p-limit@2.3.0: 988 | resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} 989 | engines: {node: '>=6'} 990 | 991 | p-locate@4.1.0: 992 | resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} 993 | engines: {node: '>=8'} 994 | 995 | p-try@2.2.0: 996 | resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} 997 | engines: {node: '>=6'} 998 | 999 | path-exists@4.0.0: 1000 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1001 | engines: {node: '>=8'} 1002 | 1003 | path-is-absolute@1.0.1: 1004 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1005 | engines: {node: '>=0.10.0'} 1006 | 1007 | path-key@3.1.1: 1008 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1009 | engines: {node: '>=8'} 1010 | 1011 | path-key@4.0.0: 1012 | resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} 1013 | engines: {node: '>=12'} 1014 | 1015 | picomatch@2.3.1: 1016 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1017 | engines: {node: '>=8.6'} 1018 | 1019 | pidtree@0.6.0: 1020 | resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} 1021 | engines: {node: '>=0.10'} 1022 | hasBin: true 1023 | 1024 | playwright-core@1.52.0: 1025 | resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} 1026 | engines: {node: '>=18'} 1027 | hasBin: true 1028 | 1029 | 'playwright-typescript@file:': 1030 | resolution: {directory: '', type: directory} 1031 | 1032 | playwright@1.52.0: 1033 | resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==} 1034 | engines: {node: '>=18'} 1035 | hasBin: true 1036 | 1037 | proxy-from-env@1.1.0: 1038 | resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} 1039 | 1040 | qs@6.14.0: 1041 | resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} 1042 | engines: {node: '>=0.6'} 1043 | 1044 | readable-stream@3.6.2: 1045 | resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1046 | engines: {node: '>= 6'} 1047 | 1048 | require-directory@2.1.1: 1049 | resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 1050 | engines: {node: '>=0.10.0'} 1051 | 1052 | require-main-filename@2.0.0: 1053 | resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} 1054 | 1055 | restore-cursor@5.1.0: 1056 | resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} 1057 | engines: {node: '>=18'} 1058 | 1059 | rfdc@1.4.1: 1060 | resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} 1061 | 1062 | safe-buffer@5.2.1: 1063 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1064 | 1065 | safer-buffer@2.1.2: 1066 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 1067 | 1068 | seq-queue@0.0.5: 1069 | resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} 1070 | 1071 | set-blocking@2.0.0: 1072 | resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} 1073 | 1074 | shebang-command@2.0.0: 1075 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1076 | engines: {node: '>=8'} 1077 | 1078 | shebang-regex@3.0.0: 1079 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1080 | engines: {node: '>=8'} 1081 | 1082 | side-channel-list@1.0.0: 1083 | resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 1084 | engines: {node: '>= 0.4'} 1085 | 1086 | side-channel-map@1.0.1: 1087 | resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 1088 | engines: {node: '>= 0.4'} 1089 | 1090 | side-channel-weakmap@1.0.2: 1091 | resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} 1092 | engines: {node: '>= 0.4'} 1093 | 1094 | side-channel@1.1.0: 1095 | resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} 1096 | engines: {node: '>= 0.4'} 1097 | 1098 | signal-exit@4.1.0: 1099 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1100 | engines: {node: '>=14'} 1101 | 1102 | slice-ansi@5.0.0: 1103 | resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} 1104 | engines: {node: '>=12'} 1105 | 1106 | slice-ansi@7.1.0: 1107 | resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} 1108 | engines: {node: '>=18'} 1109 | 1110 | sqlstring@2.3.3: 1111 | resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} 1112 | engines: {node: '>= 0.6'} 1113 | 1114 | ssf@0.11.2: 1115 | resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==} 1116 | engines: {node: '>=0.8'} 1117 | 1118 | ssh2-sftp-client@12.0.0: 1119 | resolution: {integrity: sha512-k+ocDsx6N2eDwQlIRwJFa0I1bkQpFPhIc+cv1iplaQaIPXFt9YM1ZnXCJOW4OILS5dzE+12OlhYIF5g0AzgVfg==} 1120 | engines: {node: '>=18.20.4'} 1121 | 1122 | ssh2@1.16.0: 1123 | resolution: {integrity: sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==} 1124 | engines: {node: '>=10.16.0'} 1125 | 1126 | string-argv@0.3.2: 1127 | resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} 1128 | engines: {node: '>=0.6.19'} 1129 | 1130 | string-width@4.2.3: 1131 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1132 | engines: {node: '>=8'} 1133 | 1134 | string-width@7.2.0: 1135 | resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} 1136 | engines: {node: '>=18'} 1137 | 1138 | string_decoder@1.3.0: 1139 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1140 | 1141 | strip-ansi@6.0.1: 1142 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1143 | engines: {node: '>=8'} 1144 | 1145 | strip-ansi@7.1.0: 1146 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 1147 | engines: {node: '>=12'} 1148 | 1149 | strip-final-newline@3.0.0: 1150 | resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} 1151 | engines: {node: '>=12'} 1152 | 1153 | stripe@18.1.0: 1154 | resolution: {integrity: sha512-MLDiniPTHqcfIT3anyBPmOEcaiDhYa7/jRaNypQ3Rt2SJnayQZBvVbFghIziUCZdltGAndm/ZxVOSw6uuSCDig==} 1155 | engines: {node: '>=12.*'} 1156 | peerDependencies: 1157 | '@types/node': '>=12.x.x' 1158 | peerDependenciesMeta: 1159 | '@types/node': 1160 | optional: true 1161 | 1162 | strnum@1.1.2: 1163 | resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} 1164 | 1165 | supports-color@7.2.0: 1166 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1167 | engines: {node: '>=8'} 1168 | 1169 | to-regex-range@5.0.1: 1170 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1171 | engines: {node: '>=8.0'} 1172 | 1173 | tslib@2.8.1: 1174 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1175 | 1176 | tunnel@0.0.6: 1177 | resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} 1178 | engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} 1179 | 1180 | tweetnacl@0.14.5: 1181 | resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} 1182 | 1183 | typed-rest-client@1.8.11: 1184 | resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} 1185 | 1186 | typedarray@0.0.6: 1187 | resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} 1188 | 1189 | typescript@5.8.3: 1190 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 1191 | engines: {node: '>=14.17'} 1192 | hasBin: true 1193 | 1194 | underscore@1.13.7: 1195 | resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} 1196 | 1197 | undici-types@5.26.5: 1198 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 1199 | 1200 | undici-types@6.21.0: 1201 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 1202 | 1203 | util-deprecate@1.0.2: 1204 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1205 | 1206 | uuid@9.0.1: 1207 | resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} 1208 | hasBin: true 1209 | 1210 | which-module@2.0.1: 1211 | resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} 1212 | 1213 | which@2.0.2: 1214 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1215 | engines: {node: '>= 8'} 1216 | hasBin: true 1217 | 1218 | wmf@1.0.2: 1219 | resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==} 1220 | engines: {node: '>=0.8'} 1221 | 1222 | word@0.3.0: 1223 | resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==} 1224 | engines: {node: '>=0.8'} 1225 | 1226 | wrap-ansi@6.2.0: 1227 | resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} 1228 | engines: {node: '>=8'} 1229 | 1230 | wrap-ansi@9.0.0: 1231 | resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} 1232 | engines: {node: '>=18'} 1233 | 1234 | wrappy@1.0.2: 1235 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1236 | 1237 | xlsx@0.18.5: 1238 | resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==} 1239 | engines: {node: '>=0.8'} 1240 | hasBin: true 1241 | 1242 | y18n@4.0.3: 1243 | resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} 1244 | 1245 | yaml@2.7.0: 1246 | resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} 1247 | engines: {node: '>= 14'} 1248 | hasBin: true 1249 | 1250 | yargs-parser@18.1.3: 1251 | resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} 1252 | engines: {node: '>=6'} 1253 | 1254 | yargs@15.4.1: 1255 | resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} 1256 | engines: {node: '>=8'} 1257 | 1258 | zod@3.24.3: 1259 | resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} 1260 | 1261 | snapshots: 1262 | 1263 | '@aws-crypto/crc32@5.2.0': 1264 | dependencies: 1265 | '@aws-crypto/util': 5.2.0 1266 | '@aws-sdk/types': 3.775.0 1267 | tslib: 2.8.1 1268 | 1269 | '@aws-crypto/crc32c@5.2.0': 1270 | dependencies: 1271 | '@aws-crypto/util': 5.2.0 1272 | '@aws-sdk/types': 3.775.0 1273 | tslib: 2.8.1 1274 | 1275 | '@aws-crypto/sha1-browser@5.2.0': 1276 | dependencies: 1277 | '@aws-crypto/supports-web-crypto': 5.2.0 1278 | '@aws-crypto/util': 5.2.0 1279 | '@aws-sdk/types': 3.775.0 1280 | '@aws-sdk/util-locate-window': 3.723.0 1281 | '@smithy/util-utf8': 2.3.0 1282 | tslib: 2.8.1 1283 | 1284 | '@aws-crypto/sha256-browser@5.2.0': 1285 | dependencies: 1286 | '@aws-crypto/sha256-js': 5.2.0 1287 | '@aws-crypto/supports-web-crypto': 5.2.0 1288 | '@aws-crypto/util': 5.2.0 1289 | '@aws-sdk/types': 3.775.0 1290 | '@aws-sdk/util-locate-window': 3.723.0 1291 | '@smithy/util-utf8': 2.3.0 1292 | tslib: 2.8.1 1293 | 1294 | '@aws-crypto/sha256-js@5.2.0': 1295 | dependencies: 1296 | '@aws-crypto/util': 5.2.0 1297 | '@aws-sdk/types': 3.775.0 1298 | tslib: 2.8.1 1299 | 1300 | '@aws-crypto/supports-web-crypto@5.2.0': 1301 | dependencies: 1302 | tslib: 2.8.1 1303 | 1304 | '@aws-crypto/util@5.2.0': 1305 | dependencies: 1306 | '@aws-sdk/types': 3.775.0 1307 | '@smithy/util-utf8': 2.3.0 1308 | tslib: 2.8.1 1309 | 1310 | '@aws-sdk/client-s3@3.787.0': 1311 | dependencies: 1312 | '@aws-crypto/sha1-browser': 5.2.0 1313 | '@aws-crypto/sha256-browser': 5.2.0 1314 | '@aws-crypto/sha256-js': 5.2.0 1315 | '@aws-sdk/core': 3.775.0 1316 | '@aws-sdk/credential-provider-node': 3.787.0 1317 | '@aws-sdk/middleware-bucket-endpoint': 3.775.0 1318 | '@aws-sdk/middleware-expect-continue': 3.775.0 1319 | '@aws-sdk/middleware-flexible-checksums': 3.787.0 1320 | '@aws-sdk/middleware-host-header': 3.775.0 1321 | '@aws-sdk/middleware-location-constraint': 3.775.0 1322 | '@aws-sdk/middleware-logger': 3.775.0 1323 | '@aws-sdk/middleware-recursion-detection': 3.775.0 1324 | '@aws-sdk/middleware-sdk-s3': 3.775.0 1325 | '@aws-sdk/middleware-ssec': 3.775.0 1326 | '@aws-sdk/middleware-user-agent': 3.787.0 1327 | '@aws-sdk/region-config-resolver': 3.775.0 1328 | '@aws-sdk/signature-v4-multi-region': 3.775.0 1329 | '@aws-sdk/types': 3.775.0 1330 | '@aws-sdk/util-endpoints': 3.787.0 1331 | '@aws-sdk/util-user-agent-browser': 3.775.0 1332 | '@aws-sdk/util-user-agent-node': 3.787.0 1333 | '@aws-sdk/xml-builder': 3.775.0 1334 | '@smithy/config-resolver': 4.1.0 1335 | '@smithy/core': 3.3.0 1336 | '@smithy/eventstream-serde-browser': 4.0.2 1337 | '@smithy/eventstream-serde-config-resolver': 4.1.0 1338 | '@smithy/eventstream-serde-node': 4.0.2 1339 | '@smithy/fetch-http-handler': 5.0.2 1340 | '@smithy/hash-blob-browser': 4.0.2 1341 | '@smithy/hash-node': 4.0.2 1342 | '@smithy/hash-stream-node': 4.0.2 1343 | '@smithy/invalid-dependency': 4.0.2 1344 | '@smithy/md5-js': 4.0.2 1345 | '@smithy/middleware-content-length': 4.0.2 1346 | '@smithy/middleware-endpoint': 4.1.1 1347 | '@smithy/middleware-retry': 4.1.2 1348 | '@smithy/middleware-serde': 4.0.3 1349 | '@smithy/middleware-stack': 4.0.2 1350 | '@smithy/node-config-provider': 4.0.2 1351 | '@smithy/node-http-handler': 4.0.4 1352 | '@smithy/protocol-http': 5.1.0 1353 | '@smithy/smithy-client': 4.2.1 1354 | '@smithy/types': 4.2.0 1355 | '@smithy/url-parser': 4.0.2 1356 | '@smithy/util-base64': 4.0.0 1357 | '@smithy/util-body-length-browser': 4.0.0 1358 | '@smithy/util-body-length-node': 4.0.0 1359 | '@smithy/util-defaults-mode-browser': 4.0.9 1360 | '@smithy/util-defaults-mode-node': 4.0.9 1361 | '@smithy/util-endpoints': 3.0.2 1362 | '@smithy/util-middleware': 4.0.2 1363 | '@smithy/util-retry': 4.0.3 1364 | '@smithy/util-stream': 4.2.0 1365 | '@smithy/util-utf8': 4.0.0 1366 | '@smithy/util-waiter': 4.0.3 1367 | tslib: 2.8.1 1368 | transitivePeerDependencies: 1369 | - aws-crt 1370 | 1371 | '@aws-sdk/client-sso@3.787.0': 1372 | dependencies: 1373 | '@aws-crypto/sha256-browser': 5.2.0 1374 | '@aws-crypto/sha256-js': 5.2.0 1375 | '@aws-sdk/core': 3.775.0 1376 | '@aws-sdk/middleware-host-header': 3.775.0 1377 | '@aws-sdk/middleware-logger': 3.775.0 1378 | '@aws-sdk/middleware-recursion-detection': 3.775.0 1379 | '@aws-sdk/middleware-user-agent': 3.787.0 1380 | '@aws-sdk/region-config-resolver': 3.775.0 1381 | '@aws-sdk/types': 3.775.0 1382 | '@aws-sdk/util-endpoints': 3.787.0 1383 | '@aws-sdk/util-user-agent-browser': 3.775.0 1384 | '@aws-sdk/util-user-agent-node': 3.787.0 1385 | '@smithy/config-resolver': 4.1.0 1386 | '@smithy/core': 3.3.0 1387 | '@smithy/fetch-http-handler': 5.0.2 1388 | '@smithy/hash-node': 4.0.2 1389 | '@smithy/invalid-dependency': 4.0.2 1390 | '@smithy/middleware-content-length': 4.0.2 1391 | '@smithy/middleware-endpoint': 4.1.1 1392 | '@smithy/middleware-retry': 4.1.2 1393 | '@smithy/middleware-serde': 4.0.3 1394 | '@smithy/middleware-stack': 4.0.2 1395 | '@smithy/node-config-provider': 4.0.2 1396 | '@smithy/node-http-handler': 4.0.4 1397 | '@smithy/protocol-http': 5.1.0 1398 | '@smithy/smithy-client': 4.2.1 1399 | '@smithy/types': 4.2.0 1400 | '@smithy/url-parser': 4.0.2 1401 | '@smithy/util-base64': 4.0.0 1402 | '@smithy/util-body-length-browser': 4.0.0 1403 | '@smithy/util-body-length-node': 4.0.0 1404 | '@smithy/util-defaults-mode-browser': 4.0.9 1405 | '@smithy/util-defaults-mode-node': 4.0.9 1406 | '@smithy/util-endpoints': 3.0.2 1407 | '@smithy/util-middleware': 4.0.2 1408 | '@smithy/util-retry': 4.0.3 1409 | '@smithy/util-utf8': 4.0.0 1410 | tslib: 2.8.1 1411 | transitivePeerDependencies: 1412 | - aws-crt 1413 | 1414 | '@aws-sdk/core@3.775.0': 1415 | dependencies: 1416 | '@aws-sdk/types': 3.775.0 1417 | '@smithy/core': 3.3.0 1418 | '@smithy/node-config-provider': 4.0.2 1419 | '@smithy/property-provider': 4.0.2 1420 | '@smithy/protocol-http': 5.1.0 1421 | '@smithy/signature-v4': 5.1.0 1422 | '@smithy/smithy-client': 4.2.1 1423 | '@smithy/types': 4.2.0 1424 | '@smithy/util-middleware': 4.0.2 1425 | fast-xml-parser: 4.4.1 1426 | tslib: 2.8.1 1427 | 1428 | '@aws-sdk/credential-provider-env@3.775.0': 1429 | dependencies: 1430 | '@aws-sdk/core': 3.775.0 1431 | '@aws-sdk/types': 3.775.0 1432 | '@smithy/property-provider': 4.0.2 1433 | '@smithy/types': 4.2.0 1434 | tslib: 2.8.1 1435 | 1436 | '@aws-sdk/credential-provider-http@3.775.0': 1437 | dependencies: 1438 | '@aws-sdk/core': 3.775.0 1439 | '@aws-sdk/types': 3.775.0 1440 | '@smithy/fetch-http-handler': 5.0.2 1441 | '@smithy/node-http-handler': 4.0.4 1442 | '@smithy/property-provider': 4.0.2 1443 | '@smithy/protocol-http': 5.1.0 1444 | '@smithy/smithy-client': 4.2.1 1445 | '@smithy/types': 4.2.0 1446 | '@smithy/util-stream': 4.2.0 1447 | tslib: 2.8.1 1448 | 1449 | '@aws-sdk/credential-provider-ini@3.787.0': 1450 | dependencies: 1451 | '@aws-sdk/core': 3.775.0 1452 | '@aws-sdk/credential-provider-env': 3.775.0 1453 | '@aws-sdk/credential-provider-http': 3.775.0 1454 | '@aws-sdk/credential-provider-process': 3.775.0 1455 | '@aws-sdk/credential-provider-sso': 3.787.0 1456 | '@aws-sdk/credential-provider-web-identity': 3.787.0 1457 | '@aws-sdk/nested-clients': 3.787.0 1458 | '@aws-sdk/types': 3.775.0 1459 | '@smithy/credential-provider-imds': 4.0.2 1460 | '@smithy/property-provider': 4.0.2 1461 | '@smithy/shared-ini-file-loader': 4.0.2 1462 | '@smithy/types': 4.2.0 1463 | tslib: 2.8.1 1464 | transitivePeerDependencies: 1465 | - aws-crt 1466 | 1467 | '@aws-sdk/credential-provider-node@3.787.0': 1468 | dependencies: 1469 | '@aws-sdk/credential-provider-env': 3.775.0 1470 | '@aws-sdk/credential-provider-http': 3.775.0 1471 | '@aws-sdk/credential-provider-ini': 3.787.0 1472 | '@aws-sdk/credential-provider-process': 3.775.0 1473 | '@aws-sdk/credential-provider-sso': 3.787.0 1474 | '@aws-sdk/credential-provider-web-identity': 3.787.0 1475 | '@aws-sdk/types': 3.775.0 1476 | '@smithy/credential-provider-imds': 4.0.2 1477 | '@smithy/property-provider': 4.0.2 1478 | '@smithy/shared-ini-file-loader': 4.0.2 1479 | '@smithy/types': 4.2.0 1480 | tslib: 2.8.1 1481 | transitivePeerDependencies: 1482 | - aws-crt 1483 | 1484 | '@aws-sdk/credential-provider-process@3.775.0': 1485 | dependencies: 1486 | '@aws-sdk/core': 3.775.0 1487 | '@aws-sdk/types': 3.775.0 1488 | '@smithy/property-provider': 4.0.2 1489 | '@smithy/shared-ini-file-loader': 4.0.2 1490 | '@smithy/types': 4.2.0 1491 | tslib: 2.8.1 1492 | 1493 | '@aws-sdk/credential-provider-sso@3.787.0': 1494 | dependencies: 1495 | '@aws-sdk/client-sso': 3.787.0 1496 | '@aws-sdk/core': 3.775.0 1497 | '@aws-sdk/token-providers': 3.787.0 1498 | '@aws-sdk/types': 3.775.0 1499 | '@smithy/property-provider': 4.0.2 1500 | '@smithy/shared-ini-file-loader': 4.0.2 1501 | '@smithy/types': 4.2.0 1502 | tslib: 2.8.1 1503 | transitivePeerDependencies: 1504 | - aws-crt 1505 | 1506 | '@aws-sdk/credential-provider-web-identity@3.787.0': 1507 | dependencies: 1508 | '@aws-sdk/core': 3.775.0 1509 | '@aws-sdk/nested-clients': 3.787.0 1510 | '@aws-sdk/types': 3.775.0 1511 | '@smithy/property-provider': 4.0.2 1512 | '@smithy/types': 4.2.0 1513 | tslib: 2.8.1 1514 | transitivePeerDependencies: 1515 | - aws-crt 1516 | 1517 | '@aws-sdk/middleware-bucket-endpoint@3.775.0': 1518 | dependencies: 1519 | '@aws-sdk/types': 3.775.0 1520 | '@aws-sdk/util-arn-parser': 3.723.0 1521 | '@smithy/node-config-provider': 4.0.2 1522 | '@smithy/protocol-http': 5.1.0 1523 | '@smithy/types': 4.2.0 1524 | '@smithy/util-config-provider': 4.0.0 1525 | tslib: 2.8.1 1526 | 1527 | '@aws-sdk/middleware-expect-continue@3.775.0': 1528 | dependencies: 1529 | '@aws-sdk/types': 3.775.0 1530 | '@smithy/protocol-http': 5.1.0 1531 | '@smithy/types': 4.2.0 1532 | tslib: 2.8.1 1533 | 1534 | '@aws-sdk/middleware-flexible-checksums@3.787.0': 1535 | dependencies: 1536 | '@aws-crypto/crc32': 5.2.0 1537 | '@aws-crypto/crc32c': 5.2.0 1538 | '@aws-crypto/util': 5.2.0 1539 | '@aws-sdk/core': 3.775.0 1540 | '@aws-sdk/types': 3.775.0 1541 | '@smithy/is-array-buffer': 4.0.0 1542 | '@smithy/node-config-provider': 4.0.2 1543 | '@smithy/protocol-http': 5.1.0 1544 | '@smithy/types': 4.2.0 1545 | '@smithy/util-middleware': 4.0.2 1546 | '@smithy/util-stream': 4.2.0 1547 | '@smithy/util-utf8': 4.0.0 1548 | tslib: 2.8.1 1549 | 1550 | '@aws-sdk/middleware-host-header@3.775.0': 1551 | dependencies: 1552 | '@aws-sdk/types': 3.775.0 1553 | '@smithy/protocol-http': 5.1.0 1554 | '@smithy/types': 4.2.0 1555 | tslib: 2.8.1 1556 | 1557 | '@aws-sdk/middleware-location-constraint@3.775.0': 1558 | dependencies: 1559 | '@aws-sdk/types': 3.775.0 1560 | '@smithy/types': 4.2.0 1561 | tslib: 2.8.1 1562 | 1563 | '@aws-sdk/middleware-logger@3.775.0': 1564 | dependencies: 1565 | '@aws-sdk/types': 3.775.0 1566 | '@smithy/types': 4.2.0 1567 | tslib: 2.8.1 1568 | 1569 | '@aws-sdk/middleware-recursion-detection@3.775.0': 1570 | dependencies: 1571 | '@aws-sdk/types': 3.775.0 1572 | '@smithy/protocol-http': 5.1.0 1573 | '@smithy/types': 4.2.0 1574 | tslib: 2.8.1 1575 | 1576 | '@aws-sdk/middleware-sdk-s3@3.775.0': 1577 | dependencies: 1578 | '@aws-sdk/core': 3.775.0 1579 | '@aws-sdk/types': 3.775.0 1580 | '@aws-sdk/util-arn-parser': 3.723.0 1581 | '@smithy/core': 3.3.0 1582 | '@smithy/node-config-provider': 4.0.2 1583 | '@smithy/protocol-http': 5.1.0 1584 | '@smithy/signature-v4': 5.1.0 1585 | '@smithy/smithy-client': 4.2.1 1586 | '@smithy/types': 4.2.0 1587 | '@smithy/util-config-provider': 4.0.0 1588 | '@smithy/util-middleware': 4.0.2 1589 | '@smithy/util-stream': 4.2.0 1590 | '@smithy/util-utf8': 4.0.0 1591 | tslib: 2.8.1 1592 | 1593 | '@aws-sdk/middleware-ssec@3.775.0': 1594 | dependencies: 1595 | '@aws-sdk/types': 3.775.0 1596 | '@smithy/types': 4.2.0 1597 | tslib: 2.8.1 1598 | 1599 | '@aws-sdk/middleware-user-agent@3.787.0': 1600 | dependencies: 1601 | '@aws-sdk/core': 3.775.0 1602 | '@aws-sdk/types': 3.775.0 1603 | '@aws-sdk/util-endpoints': 3.787.0 1604 | '@smithy/core': 3.3.0 1605 | '@smithy/protocol-http': 5.1.0 1606 | '@smithy/types': 4.2.0 1607 | tslib: 2.8.1 1608 | 1609 | '@aws-sdk/nested-clients@3.787.0': 1610 | dependencies: 1611 | '@aws-crypto/sha256-browser': 5.2.0 1612 | '@aws-crypto/sha256-js': 5.2.0 1613 | '@aws-sdk/core': 3.775.0 1614 | '@aws-sdk/middleware-host-header': 3.775.0 1615 | '@aws-sdk/middleware-logger': 3.775.0 1616 | '@aws-sdk/middleware-recursion-detection': 3.775.0 1617 | '@aws-sdk/middleware-user-agent': 3.787.0 1618 | '@aws-sdk/region-config-resolver': 3.775.0 1619 | '@aws-sdk/types': 3.775.0 1620 | '@aws-sdk/util-endpoints': 3.787.0 1621 | '@aws-sdk/util-user-agent-browser': 3.775.0 1622 | '@aws-sdk/util-user-agent-node': 3.787.0 1623 | '@smithy/config-resolver': 4.1.0 1624 | '@smithy/core': 3.3.0 1625 | '@smithy/fetch-http-handler': 5.0.2 1626 | '@smithy/hash-node': 4.0.2 1627 | '@smithy/invalid-dependency': 4.0.2 1628 | '@smithy/middleware-content-length': 4.0.2 1629 | '@smithy/middleware-endpoint': 4.1.1 1630 | '@smithy/middleware-retry': 4.1.2 1631 | '@smithy/middleware-serde': 4.0.3 1632 | '@smithy/middleware-stack': 4.0.2 1633 | '@smithy/node-config-provider': 4.0.2 1634 | '@smithy/node-http-handler': 4.0.4 1635 | '@smithy/protocol-http': 5.1.0 1636 | '@smithy/smithy-client': 4.2.1 1637 | '@smithy/types': 4.2.0 1638 | '@smithy/url-parser': 4.0.2 1639 | '@smithy/util-base64': 4.0.0 1640 | '@smithy/util-body-length-browser': 4.0.0 1641 | '@smithy/util-body-length-node': 4.0.0 1642 | '@smithy/util-defaults-mode-browser': 4.0.9 1643 | '@smithy/util-defaults-mode-node': 4.0.9 1644 | '@smithy/util-endpoints': 3.0.2 1645 | '@smithy/util-middleware': 4.0.2 1646 | '@smithy/util-retry': 4.0.3 1647 | '@smithy/util-utf8': 4.0.0 1648 | tslib: 2.8.1 1649 | transitivePeerDependencies: 1650 | - aws-crt 1651 | 1652 | '@aws-sdk/region-config-resolver@3.775.0': 1653 | dependencies: 1654 | '@aws-sdk/types': 3.775.0 1655 | '@smithy/node-config-provider': 4.0.2 1656 | '@smithy/types': 4.2.0 1657 | '@smithy/util-config-provider': 4.0.0 1658 | '@smithy/util-middleware': 4.0.2 1659 | tslib: 2.8.1 1660 | 1661 | '@aws-sdk/signature-v4-multi-region@3.775.0': 1662 | dependencies: 1663 | '@aws-sdk/middleware-sdk-s3': 3.775.0 1664 | '@aws-sdk/types': 3.775.0 1665 | '@smithy/protocol-http': 5.1.0 1666 | '@smithy/signature-v4': 5.1.0 1667 | '@smithy/types': 4.2.0 1668 | tslib: 2.8.1 1669 | 1670 | '@aws-sdk/token-providers@3.787.0': 1671 | dependencies: 1672 | '@aws-sdk/nested-clients': 3.787.0 1673 | '@aws-sdk/types': 3.775.0 1674 | '@smithy/property-provider': 4.0.2 1675 | '@smithy/shared-ini-file-loader': 4.0.2 1676 | '@smithy/types': 4.2.0 1677 | tslib: 2.8.1 1678 | transitivePeerDependencies: 1679 | - aws-crt 1680 | 1681 | '@aws-sdk/types@3.775.0': 1682 | dependencies: 1683 | '@smithy/types': 4.2.0 1684 | tslib: 2.8.1 1685 | 1686 | '@aws-sdk/util-arn-parser@3.723.0': 1687 | dependencies: 1688 | tslib: 2.8.1 1689 | 1690 | '@aws-sdk/util-endpoints@3.787.0': 1691 | dependencies: 1692 | '@aws-sdk/types': 3.775.0 1693 | '@smithy/types': 4.2.0 1694 | '@smithy/util-endpoints': 3.0.2 1695 | tslib: 2.8.1 1696 | 1697 | '@aws-sdk/util-locate-window@3.723.0': 1698 | dependencies: 1699 | tslib: 2.8.1 1700 | 1701 | '@aws-sdk/util-user-agent-browser@3.775.0': 1702 | dependencies: 1703 | '@aws-sdk/types': 3.775.0 1704 | '@smithy/types': 4.2.0 1705 | bowser: 2.11.0 1706 | tslib: 2.8.1 1707 | 1708 | '@aws-sdk/util-user-agent-node@3.787.0': 1709 | dependencies: 1710 | '@aws-sdk/middleware-user-agent': 3.787.0 1711 | '@aws-sdk/types': 3.775.0 1712 | '@smithy/node-config-provider': 4.0.2 1713 | '@smithy/types': 4.2.0 1714 | tslib: 2.8.1 1715 | 1716 | '@aws-sdk/xml-builder@3.775.0': 1717 | dependencies: 1718 | '@smithy/types': 4.2.0 1719 | tslib: 2.8.1 1720 | 1721 | '@biomejs/biome@1.9.4': 1722 | optionalDependencies: 1723 | '@biomejs/cli-darwin-arm64': 1.9.4 1724 | '@biomejs/cli-darwin-x64': 1.9.4 1725 | '@biomejs/cli-linux-arm64': 1.9.4 1726 | '@biomejs/cli-linux-arm64-musl': 1.9.4 1727 | '@biomejs/cli-linux-x64': 1.9.4 1728 | '@biomejs/cli-linux-x64-musl': 1.9.4 1729 | '@biomejs/cli-win32-arm64': 1.9.4 1730 | '@biomejs/cli-win32-x64': 1.9.4 1731 | 1732 | '@biomejs/cli-darwin-arm64@1.9.4': 1733 | optional: true 1734 | 1735 | '@biomejs/cli-darwin-x64@1.9.4': 1736 | optional: true 1737 | 1738 | '@biomejs/cli-linux-arm64-musl@1.9.4': 1739 | optional: true 1740 | 1741 | '@biomejs/cli-linux-arm64@1.9.4': 1742 | optional: true 1743 | 1744 | '@biomejs/cli-linux-x64-musl@1.9.4': 1745 | optional: true 1746 | 1747 | '@biomejs/cli-linux-x64@1.9.4': 1748 | optional: true 1749 | 1750 | '@biomejs/cli-win32-arm64@1.9.4': 1751 | optional: true 1752 | 1753 | '@biomejs/cli-win32-x64@1.9.4': 1754 | optional: true 1755 | 1756 | '@playwright/test@1.52.0': 1757 | dependencies: 1758 | playwright: 1.52.0 1759 | 1760 | '@smithy/abort-controller@4.0.2': 1761 | dependencies: 1762 | '@smithy/types': 4.2.0 1763 | tslib: 2.8.1 1764 | 1765 | '@smithy/chunked-blob-reader-native@4.0.0': 1766 | dependencies: 1767 | '@smithy/util-base64': 4.0.0 1768 | tslib: 2.8.1 1769 | 1770 | '@smithy/chunked-blob-reader@5.0.0': 1771 | dependencies: 1772 | tslib: 2.8.1 1773 | 1774 | '@smithy/config-resolver@4.1.0': 1775 | dependencies: 1776 | '@smithy/node-config-provider': 4.0.2 1777 | '@smithy/types': 4.2.0 1778 | '@smithy/util-config-provider': 4.0.0 1779 | '@smithy/util-middleware': 4.0.2 1780 | tslib: 2.8.1 1781 | 1782 | '@smithy/core@3.3.0': 1783 | dependencies: 1784 | '@smithy/middleware-serde': 4.0.3 1785 | '@smithy/protocol-http': 5.1.0 1786 | '@smithy/types': 4.2.0 1787 | '@smithy/util-body-length-browser': 4.0.0 1788 | '@smithy/util-middleware': 4.0.2 1789 | '@smithy/util-stream': 4.2.0 1790 | '@smithy/util-utf8': 4.0.0 1791 | tslib: 2.8.1 1792 | 1793 | '@smithy/credential-provider-imds@4.0.2': 1794 | dependencies: 1795 | '@smithy/node-config-provider': 4.0.2 1796 | '@smithy/property-provider': 4.0.2 1797 | '@smithy/types': 4.2.0 1798 | '@smithy/url-parser': 4.0.2 1799 | tslib: 2.8.1 1800 | 1801 | '@smithy/eventstream-codec@4.0.2': 1802 | dependencies: 1803 | '@aws-crypto/crc32': 5.2.0 1804 | '@smithy/types': 4.2.0 1805 | '@smithy/util-hex-encoding': 4.0.0 1806 | tslib: 2.8.1 1807 | 1808 | '@smithy/eventstream-serde-browser@4.0.2': 1809 | dependencies: 1810 | '@smithy/eventstream-serde-universal': 4.0.2 1811 | '@smithy/types': 4.2.0 1812 | tslib: 2.8.1 1813 | 1814 | '@smithy/eventstream-serde-config-resolver@4.1.0': 1815 | dependencies: 1816 | '@smithy/types': 4.2.0 1817 | tslib: 2.8.1 1818 | 1819 | '@smithy/eventstream-serde-node@4.0.2': 1820 | dependencies: 1821 | '@smithy/eventstream-serde-universal': 4.0.2 1822 | '@smithy/types': 4.2.0 1823 | tslib: 2.8.1 1824 | 1825 | '@smithy/eventstream-serde-universal@4.0.2': 1826 | dependencies: 1827 | '@smithy/eventstream-codec': 4.0.2 1828 | '@smithy/types': 4.2.0 1829 | tslib: 2.8.1 1830 | 1831 | '@smithy/fetch-http-handler@5.0.2': 1832 | dependencies: 1833 | '@smithy/protocol-http': 5.1.0 1834 | '@smithy/querystring-builder': 4.0.2 1835 | '@smithy/types': 4.2.0 1836 | '@smithy/util-base64': 4.0.0 1837 | tslib: 2.8.1 1838 | 1839 | '@smithy/hash-blob-browser@4.0.2': 1840 | dependencies: 1841 | '@smithy/chunked-blob-reader': 5.0.0 1842 | '@smithy/chunked-blob-reader-native': 4.0.0 1843 | '@smithy/types': 4.2.0 1844 | tslib: 2.8.1 1845 | 1846 | '@smithy/hash-node@4.0.2': 1847 | dependencies: 1848 | '@smithy/types': 4.2.0 1849 | '@smithy/util-buffer-from': 4.0.0 1850 | '@smithy/util-utf8': 4.0.0 1851 | tslib: 2.8.1 1852 | 1853 | '@smithy/hash-stream-node@4.0.2': 1854 | dependencies: 1855 | '@smithy/types': 4.2.0 1856 | '@smithy/util-utf8': 4.0.0 1857 | tslib: 2.8.1 1858 | 1859 | '@smithy/invalid-dependency@4.0.2': 1860 | dependencies: 1861 | '@smithy/types': 4.2.0 1862 | tslib: 2.8.1 1863 | 1864 | '@smithy/is-array-buffer@2.2.0': 1865 | dependencies: 1866 | tslib: 2.8.1 1867 | 1868 | '@smithy/is-array-buffer@4.0.0': 1869 | dependencies: 1870 | tslib: 2.8.1 1871 | 1872 | '@smithy/md5-js@4.0.2': 1873 | dependencies: 1874 | '@smithy/types': 4.2.0 1875 | '@smithy/util-utf8': 4.0.0 1876 | tslib: 2.8.1 1877 | 1878 | '@smithy/middleware-content-length@4.0.2': 1879 | dependencies: 1880 | '@smithy/protocol-http': 5.1.0 1881 | '@smithy/types': 4.2.0 1882 | tslib: 2.8.1 1883 | 1884 | '@smithy/middleware-endpoint@4.1.1': 1885 | dependencies: 1886 | '@smithy/core': 3.3.0 1887 | '@smithy/middleware-serde': 4.0.3 1888 | '@smithy/node-config-provider': 4.0.2 1889 | '@smithy/shared-ini-file-loader': 4.0.2 1890 | '@smithy/types': 4.2.0 1891 | '@smithy/url-parser': 4.0.2 1892 | '@smithy/util-middleware': 4.0.2 1893 | tslib: 2.8.1 1894 | 1895 | '@smithy/middleware-retry@4.1.2': 1896 | dependencies: 1897 | '@smithy/node-config-provider': 4.0.2 1898 | '@smithy/protocol-http': 5.1.0 1899 | '@smithy/service-error-classification': 4.0.3 1900 | '@smithy/smithy-client': 4.2.1 1901 | '@smithy/types': 4.2.0 1902 | '@smithy/util-middleware': 4.0.2 1903 | '@smithy/util-retry': 4.0.3 1904 | tslib: 2.8.1 1905 | uuid: 9.0.1 1906 | 1907 | '@smithy/middleware-serde@4.0.3': 1908 | dependencies: 1909 | '@smithy/types': 4.2.0 1910 | tslib: 2.8.1 1911 | 1912 | '@smithy/middleware-stack@4.0.2': 1913 | dependencies: 1914 | '@smithy/types': 4.2.0 1915 | tslib: 2.8.1 1916 | 1917 | '@smithy/node-config-provider@4.0.2': 1918 | dependencies: 1919 | '@smithy/property-provider': 4.0.2 1920 | '@smithy/shared-ini-file-loader': 4.0.2 1921 | '@smithy/types': 4.2.0 1922 | tslib: 2.8.1 1923 | 1924 | '@smithy/node-http-handler@4.0.4': 1925 | dependencies: 1926 | '@smithy/abort-controller': 4.0.2 1927 | '@smithy/protocol-http': 5.1.0 1928 | '@smithy/querystring-builder': 4.0.2 1929 | '@smithy/types': 4.2.0 1930 | tslib: 2.8.1 1931 | 1932 | '@smithy/property-provider@4.0.2': 1933 | dependencies: 1934 | '@smithy/types': 4.2.0 1935 | tslib: 2.8.1 1936 | 1937 | '@smithy/protocol-http@5.1.0': 1938 | dependencies: 1939 | '@smithy/types': 4.2.0 1940 | tslib: 2.8.1 1941 | 1942 | '@smithy/querystring-builder@4.0.2': 1943 | dependencies: 1944 | '@smithy/types': 4.2.0 1945 | '@smithy/util-uri-escape': 4.0.0 1946 | tslib: 2.8.1 1947 | 1948 | '@smithy/querystring-parser@4.0.2': 1949 | dependencies: 1950 | '@smithy/types': 4.2.0 1951 | tslib: 2.8.1 1952 | 1953 | '@smithy/service-error-classification@4.0.3': 1954 | dependencies: 1955 | '@smithy/types': 4.2.0 1956 | 1957 | '@smithy/shared-ini-file-loader@4.0.2': 1958 | dependencies: 1959 | '@smithy/types': 4.2.0 1960 | tslib: 2.8.1 1961 | 1962 | '@smithy/signature-v4@5.1.0': 1963 | dependencies: 1964 | '@smithy/is-array-buffer': 4.0.0 1965 | '@smithy/protocol-http': 5.1.0 1966 | '@smithy/types': 4.2.0 1967 | '@smithy/util-hex-encoding': 4.0.0 1968 | '@smithy/util-middleware': 4.0.2 1969 | '@smithy/util-uri-escape': 4.0.0 1970 | '@smithy/util-utf8': 4.0.0 1971 | tslib: 2.8.1 1972 | 1973 | '@smithy/smithy-client@4.2.1': 1974 | dependencies: 1975 | '@smithy/core': 3.3.0 1976 | '@smithy/middleware-endpoint': 4.1.1 1977 | '@smithy/middleware-stack': 4.0.2 1978 | '@smithy/protocol-http': 5.1.0 1979 | '@smithy/types': 4.2.0 1980 | '@smithy/util-stream': 4.2.0 1981 | tslib: 2.8.1 1982 | 1983 | '@smithy/types@4.2.0': 1984 | dependencies: 1985 | tslib: 2.8.1 1986 | 1987 | '@smithy/url-parser@4.0.2': 1988 | dependencies: 1989 | '@smithy/querystring-parser': 4.0.2 1990 | '@smithy/types': 4.2.0 1991 | tslib: 2.8.1 1992 | 1993 | '@smithy/util-base64@4.0.0': 1994 | dependencies: 1995 | '@smithy/util-buffer-from': 4.0.0 1996 | '@smithy/util-utf8': 4.0.0 1997 | tslib: 2.8.1 1998 | 1999 | '@smithy/util-body-length-browser@4.0.0': 2000 | dependencies: 2001 | tslib: 2.8.1 2002 | 2003 | '@smithy/util-body-length-node@4.0.0': 2004 | dependencies: 2005 | tslib: 2.8.1 2006 | 2007 | '@smithy/util-buffer-from@2.2.0': 2008 | dependencies: 2009 | '@smithy/is-array-buffer': 2.2.0 2010 | tslib: 2.8.1 2011 | 2012 | '@smithy/util-buffer-from@4.0.0': 2013 | dependencies: 2014 | '@smithy/is-array-buffer': 4.0.0 2015 | tslib: 2.8.1 2016 | 2017 | '@smithy/util-config-provider@4.0.0': 2018 | dependencies: 2019 | tslib: 2.8.1 2020 | 2021 | '@smithy/util-defaults-mode-browser@4.0.9': 2022 | dependencies: 2023 | '@smithy/property-provider': 4.0.2 2024 | '@smithy/smithy-client': 4.2.1 2025 | '@smithy/types': 4.2.0 2026 | bowser: 2.11.0 2027 | tslib: 2.8.1 2028 | 2029 | '@smithy/util-defaults-mode-node@4.0.9': 2030 | dependencies: 2031 | '@smithy/config-resolver': 4.1.0 2032 | '@smithy/credential-provider-imds': 4.0.2 2033 | '@smithy/node-config-provider': 4.0.2 2034 | '@smithy/property-provider': 4.0.2 2035 | '@smithy/smithy-client': 4.2.1 2036 | '@smithy/types': 4.2.0 2037 | tslib: 2.8.1 2038 | 2039 | '@smithy/util-endpoints@3.0.2': 2040 | dependencies: 2041 | '@smithy/node-config-provider': 4.0.2 2042 | '@smithy/types': 4.2.0 2043 | tslib: 2.8.1 2044 | 2045 | '@smithy/util-hex-encoding@4.0.0': 2046 | dependencies: 2047 | tslib: 2.8.1 2048 | 2049 | '@smithy/util-middleware@4.0.2': 2050 | dependencies: 2051 | '@smithy/types': 4.2.0 2052 | tslib: 2.8.1 2053 | 2054 | '@smithy/util-retry@4.0.3': 2055 | dependencies: 2056 | '@smithy/service-error-classification': 4.0.3 2057 | '@smithy/types': 4.2.0 2058 | tslib: 2.8.1 2059 | 2060 | '@smithy/util-stream@4.2.0': 2061 | dependencies: 2062 | '@smithy/fetch-http-handler': 5.0.2 2063 | '@smithy/node-http-handler': 4.0.4 2064 | '@smithy/types': 4.2.0 2065 | '@smithy/util-base64': 4.0.0 2066 | '@smithy/util-buffer-from': 4.0.0 2067 | '@smithy/util-hex-encoding': 4.0.0 2068 | '@smithy/util-utf8': 4.0.0 2069 | tslib: 2.8.1 2070 | 2071 | '@smithy/util-uri-escape@4.0.0': 2072 | dependencies: 2073 | tslib: 2.8.1 2074 | 2075 | '@smithy/util-utf8@2.3.0': 2076 | dependencies: 2077 | '@smithy/util-buffer-from': 2.2.0 2078 | tslib: 2.8.1 2079 | 2080 | '@smithy/util-utf8@4.0.0': 2081 | dependencies: 2082 | '@smithy/util-buffer-from': 4.0.0 2083 | tslib: 2.8.1 2084 | 2085 | '@smithy/util-waiter@4.0.3': 2086 | dependencies: 2087 | '@smithy/abort-controller': 4.0.2 2088 | '@smithy/types': 4.2.0 2089 | tslib: 2.8.1 2090 | 2091 | '@types/node@13.13.52': {} 2092 | 2093 | '@types/node@18.19.86': 2094 | dependencies: 2095 | undici-types: 5.26.5 2096 | 2097 | '@types/node@22.15.3': 2098 | dependencies: 2099 | undici-types: 6.21.0 2100 | 2101 | '@types/ssh2-sftp-client@9.0.4': 2102 | dependencies: 2103 | '@types/ssh2': 1.15.5 2104 | 2105 | '@types/ssh2@1.15.5': 2106 | dependencies: 2107 | '@types/node': 18.19.86 2108 | 2109 | '@visual-regression-tracker/agent-playwright@5.3.1': 2110 | dependencies: 2111 | '@visual-regression-tracker/sdk-js': 5.7.1 2112 | transitivePeerDependencies: 2113 | - debug 2114 | 2115 | '@visual-regression-tracker/sdk-js@5.7.1': 2116 | dependencies: 2117 | axios: 1.8.4 2118 | form-data: 4.0.2 2119 | transitivePeerDependencies: 2120 | - debug 2121 | 2122 | adler-32@1.3.1: {} 2123 | 2124 | allure-js-commons@3.2.1(allure-playwright@3.2.1(@playwright/test@1.52.0)): 2125 | dependencies: 2126 | md5: 2.3.0 2127 | optionalDependencies: 2128 | allure-playwright: 3.2.1(@playwright/test@1.52.0) 2129 | 2130 | allure-playwright@3.2.1(@playwright/test@1.52.0): 2131 | dependencies: 2132 | '@playwright/test': 1.52.0 2133 | allure-js-commons: 3.2.1(allure-playwright@3.2.1(@playwright/test@1.52.0)) 2134 | 2135 | ansi-escapes@7.0.0: 2136 | dependencies: 2137 | environment: 1.1.0 2138 | 2139 | ansi-regex@5.0.1: {} 2140 | 2141 | ansi-regex@6.0.1: {} 2142 | 2143 | ansi-styles@4.3.0: 2144 | dependencies: 2145 | color-convert: 2.0.1 2146 | 2147 | ansi-styles@6.2.1: {} 2148 | 2149 | asn1@0.2.6: 2150 | dependencies: 2151 | safer-buffer: 2.1.2 2152 | 2153 | asynckit@0.4.0: {} 2154 | 2155 | aws-ssl-profiles@1.1.2: {} 2156 | 2157 | axios@1.8.4: 2158 | dependencies: 2159 | follow-redirects: 1.15.9 2160 | form-data: 4.0.2 2161 | proxy-from-env: 1.1.0 2162 | transitivePeerDependencies: 2163 | - debug 2164 | 2165 | balanced-match@1.0.2: {} 2166 | 2167 | bcrypt-pbkdf@1.0.2: 2168 | dependencies: 2169 | tweetnacl: 0.14.5 2170 | 2171 | bowser@2.11.0: {} 2172 | 2173 | brace-expansion@1.1.11: 2174 | dependencies: 2175 | balanced-match: 1.0.2 2176 | concat-map: 0.0.1 2177 | 2178 | braces@3.0.3: 2179 | dependencies: 2180 | fill-range: 7.1.1 2181 | 2182 | buffer-from@1.1.2: {} 2183 | 2184 | buildcheck@0.0.6: 2185 | optional: true 2186 | 2187 | call-bind-apply-helpers@1.0.2: 2188 | dependencies: 2189 | es-errors: 1.3.0 2190 | function-bind: 1.1.2 2191 | 2192 | call-bound@1.0.4: 2193 | dependencies: 2194 | call-bind-apply-helpers: 1.0.2 2195 | get-intrinsic: 1.3.0 2196 | 2197 | camelcase@5.3.1: {} 2198 | 2199 | cfb@1.2.2: 2200 | dependencies: 2201 | adler-32: 1.3.1 2202 | crc-32: 1.2.2 2203 | 2204 | chalk@4.1.2: 2205 | dependencies: 2206 | ansi-styles: 4.3.0 2207 | supports-color: 7.2.0 2208 | 2209 | chalk@5.4.1: {} 2210 | 2211 | charenc@0.0.2: {} 2212 | 2213 | cli-cursor@5.0.0: 2214 | dependencies: 2215 | restore-cursor: 5.1.0 2216 | 2217 | cli-truncate@4.0.0: 2218 | dependencies: 2219 | slice-ansi: 5.0.0 2220 | string-width: 7.2.0 2221 | 2222 | cliui@6.0.0: 2223 | dependencies: 2224 | string-width: 4.2.3 2225 | strip-ansi: 6.0.1 2226 | wrap-ansi: 6.2.0 2227 | 2228 | codepage@1.15.0: {} 2229 | 2230 | color-convert@2.0.1: 2231 | dependencies: 2232 | color-name: 1.1.4 2233 | 2234 | color-name@1.1.4: {} 2235 | 2236 | colorette@2.0.20: {} 2237 | 2238 | combined-stream@1.0.8: 2239 | dependencies: 2240 | delayed-stream: 1.0.0 2241 | 2242 | commander@13.1.0: {} 2243 | 2244 | concat-map@0.0.1: {} 2245 | 2246 | concat-stream@2.0.0: 2247 | dependencies: 2248 | buffer-from: 1.1.2 2249 | inherits: 2.0.4 2250 | readable-stream: 3.6.2 2251 | typedarray: 0.0.6 2252 | 2253 | cpu-features@0.0.10: 2254 | dependencies: 2255 | buildcheck: 0.0.6 2256 | nan: 2.22.2 2257 | optional: true 2258 | 2259 | crc-32@1.2.2: {} 2260 | 2261 | create-index@2.6.0: 2262 | dependencies: 2263 | chalk: 4.1.2 2264 | glob: 7.2.3 2265 | lodash: 4.17.21 2266 | moment: 2.30.1 2267 | yargs: 15.4.1 2268 | 2269 | cross-spawn@7.0.6: 2270 | dependencies: 2271 | path-key: 3.1.1 2272 | shebang-command: 2.0.0 2273 | which: 2.0.2 2274 | 2275 | crypt@0.0.2: {} 2276 | 2277 | debug@4.4.0: 2278 | dependencies: 2279 | ms: 2.1.3 2280 | 2281 | decamelize@1.2.0: {} 2282 | 2283 | delayed-stream@1.0.0: {} 2284 | 2285 | denque@2.1.0: {} 2286 | 2287 | dotenv@16.5.0: {} 2288 | 2289 | dunder-proto@1.0.1: 2290 | dependencies: 2291 | call-bind-apply-helpers: 1.0.2 2292 | es-errors: 1.3.0 2293 | gopd: 1.2.0 2294 | 2295 | emoji-regex@10.3.0: {} 2296 | 2297 | emoji-regex@8.0.0: {} 2298 | 2299 | environment@1.1.0: {} 2300 | 2301 | es-define-property@1.0.1: {} 2302 | 2303 | es-errors@1.3.0: {} 2304 | 2305 | es-object-atoms@1.1.1: 2306 | dependencies: 2307 | es-errors: 1.3.0 2308 | 2309 | es-set-tostringtag@2.1.0: 2310 | dependencies: 2311 | es-errors: 1.3.0 2312 | get-intrinsic: 1.3.0 2313 | has-tostringtag: 1.0.2 2314 | hasown: 2.0.2 2315 | 2316 | eventemitter3@5.0.1: {} 2317 | 2318 | execa@8.0.1: 2319 | dependencies: 2320 | cross-spawn: 7.0.6 2321 | get-stream: 8.0.1 2322 | human-signals: 5.0.0 2323 | is-stream: 3.0.0 2324 | merge-stream: 2.0.0 2325 | npm-run-path: 5.3.0 2326 | onetime: 6.0.0 2327 | signal-exit: 4.1.0 2328 | strip-final-newline: 3.0.0 2329 | 2330 | fast-xml-parser@4.4.1: 2331 | dependencies: 2332 | strnum: 1.1.2 2333 | 2334 | fill-range@7.1.1: 2335 | dependencies: 2336 | to-regex-range: 5.0.1 2337 | 2338 | find-up@4.1.0: 2339 | dependencies: 2340 | locate-path: 5.0.0 2341 | path-exists: 4.0.0 2342 | 2343 | follow-redirects@1.15.9: {} 2344 | 2345 | form-data@4.0.2: 2346 | dependencies: 2347 | asynckit: 0.4.0 2348 | combined-stream: 1.0.8 2349 | es-set-tostringtag: 2.1.0 2350 | mime-types: 2.1.35 2351 | 2352 | frac@1.1.2: {} 2353 | 2354 | fs.realpath@1.0.0: {} 2355 | 2356 | fs@0.0.1-security: {} 2357 | 2358 | fsevents@2.3.2: 2359 | optional: true 2360 | 2361 | function-bind@1.1.2: {} 2362 | 2363 | generate-function@2.3.1: 2364 | dependencies: 2365 | is-property: 1.0.2 2366 | 2367 | get-caller-file@2.0.5: {} 2368 | 2369 | get-east-asian-width@1.2.0: {} 2370 | 2371 | get-intrinsic@1.3.0: 2372 | dependencies: 2373 | call-bind-apply-helpers: 1.0.2 2374 | es-define-property: 1.0.1 2375 | es-errors: 1.3.0 2376 | es-object-atoms: 1.1.1 2377 | function-bind: 1.1.2 2378 | get-proto: 1.0.1 2379 | gopd: 1.2.0 2380 | has-symbols: 1.1.0 2381 | hasown: 2.0.2 2382 | math-intrinsics: 1.1.0 2383 | 2384 | get-proto@1.0.1: 2385 | dependencies: 2386 | dunder-proto: 1.0.1 2387 | es-object-atoms: 1.1.1 2388 | 2389 | get-stream@8.0.1: {} 2390 | 2391 | glob@7.2.3: 2392 | dependencies: 2393 | fs.realpath: 1.0.0 2394 | inflight: 1.0.6 2395 | inherits: 2.0.4 2396 | minimatch: 3.1.2 2397 | once: 1.4.0 2398 | path-is-absolute: 1.0.1 2399 | 2400 | gopd@1.2.0: {} 2401 | 2402 | has-flag@4.0.0: {} 2403 | 2404 | has-symbols@1.1.0: {} 2405 | 2406 | has-tostringtag@1.0.2: 2407 | dependencies: 2408 | has-symbols: 1.1.0 2409 | 2410 | hasown@2.0.2: 2411 | dependencies: 2412 | function-bind: 1.1.2 2413 | 2414 | human-signals@5.0.0: {} 2415 | 2416 | husky@9.1.7: {} 2417 | 2418 | iconv-lite@0.6.3: 2419 | dependencies: 2420 | safer-buffer: 2.1.2 2421 | 2422 | inflight@1.0.6: 2423 | dependencies: 2424 | once: 1.4.0 2425 | wrappy: 1.0.2 2426 | 2427 | inherits@2.0.4: {} 2428 | 2429 | is-buffer@1.1.6: {} 2430 | 2431 | is-fullwidth-code-point@3.0.0: {} 2432 | 2433 | is-fullwidth-code-point@4.0.0: {} 2434 | 2435 | is-fullwidth-code-point@5.0.0: 2436 | dependencies: 2437 | get-east-asian-width: 1.2.0 2438 | 2439 | is-number@7.0.0: {} 2440 | 2441 | is-property@1.0.2: {} 2442 | 2443 | is-stream@3.0.0: {} 2444 | 2445 | isexe@2.0.0: {} 2446 | 2447 | lilconfig@3.1.3: {} 2448 | 2449 | lint-staged@15.5.1: 2450 | dependencies: 2451 | chalk: 5.4.1 2452 | commander: 13.1.0 2453 | debug: 4.4.0 2454 | execa: 8.0.1 2455 | lilconfig: 3.1.3 2456 | listr2: 8.2.5 2457 | micromatch: 4.0.8 2458 | pidtree: 0.6.0 2459 | string-argv: 0.3.2 2460 | yaml: 2.7.0 2461 | transitivePeerDependencies: 2462 | - supports-color 2463 | 2464 | listr2@8.2.5: 2465 | dependencies: 2466 | cli-truncate: 4.0.0 2467 | colorette: 2.0.20 2468 | eventemitter3: 5.0.1 2469 | log-update: 6.1.0 2470 | rfdc: 1.4.1 2471 | wrap-ansi: 9.0.0 2472 | 2473 | locate-path@5.0.0: 2474 | dependencies: 2475 | p-locate: 4.1.0 2476 | 2477 | lodash@4.17.21: {} 2478 | 2479 | log-update@6.1.0: 2480 | dependencies: 2481 | ansi-escapes: 7.0.0 2482 | cli-cursor: 5.0.0 2483 | slice-ansi: 7.1.0 2484 | strip-ansi: 7.1.0 2485 | wrap-ansi: 9.0.0 2486 | 2487 | long@5.3.1: {} 2488 | 2489 | lru-cache@7.18.3: {} 2490 | 2491 | lru.min@1.1.2: {} 2492 | 2493 | mailinator-client@1.0.5: 2494 | dependencies: 2495 | '@types/node': 13.13.52 2496 | create-index: 2.6.0 2497 | fs: 0.0.1-security 2498 | mailinator-client: 'playwright-typescript@file:' 2499 | typed-rest-client: 1.8.11 2500 | 2501 | math-intrinsics@1.1.0: {} 2502 | 2503 | md5@2.3.0: 2504 | dependencies: 2505 | charenc: 0.0.2 2506 | crypt: 0.0.2 2507 | is-buffer: 1.1.6 2508 | 2509 | merge-stream@2.0.0: {} 2510 | 2511 | micromatch@4.0.8: 2512 | dependencies: 2513 | braces: 3.0.3 2514 | picomatch: 2.3.1 2515 | 2516 | mime-db@1.52.0: {} 2517 | 2518 | mime-types@2.1.35: 2519 | dependencies: 2520 | mime-db: 1.52.0 2521 | 2522 | mimic-fn@4.0.0: {} 2523 | 2524 | mimic-function@5.0.1: {} 2525 | 2526 | minimatch@3.1.2: 2527 | dependencies: 2528 | brace-expansion: 1.1.11 2529 | 2530 | moment@2.30.1: {} 2531 | 2532 | ms@2.1.3: {} 2533 | 2534 | mysql2@3.14.0: 2535 | dependencies: 2536 | aws-ssl-profiles: 1.1.2 2537 | denque: 2.1.0 2538 | generate-function: 2.3.1 2539 | iconv-lite: 0.6.3 2540 | long: 5.3.1 2541 | lru.min: 1.1.2 2542 | named-placeholders: 1.1.3 2543 | seq-queue: 0.0.5 2544 | sqlstring: 2.3.3 2545 | 2546 | named-placeholders@1.1.3: 2547 | dependencies: 2548 | lru-cache: 7.18.3 2549 | 2550 | nan@2.22.2: 2551 | optional: true 2552 | 2553 | npm-run-path@5.3.0: 2554 | dependencies: 2555 | path-key: 4.0.0 2556 | 2557 | object-inspect@1.13.4: {} 2558 | 2559 | once@1.4.0: 2560 | dependencies: 2561 | wrappy: 1.0.2 2562 | 2563 | onetime@6.0.0: 2564 | dependencies: 2565 | mimic-fn: 4.0.0 2566 | 2567 | onetime@7.0.0: 2568 | dependencies: 2569 | mimic-function: 5.0.1 2570 | 2571 | p-limit@2.3.0: 2572 | dependencies: 2573 | p-try: 2.2.0 2574 | 2575 | p-locate@4.1.0: 2576 | dependencies: 2577 | p-limit: 2.3.0 2578 | 2579 | p-try@2.2.0: {} 2580 | 2581 | path-exists@4.0.0: {} 2582 | 2583 | path-is-absolute@1.0.1: {} 2584 | 2585 | path-key@3.1.1: {} 2586 | 2587 | path-key@4.0.0: {} 2588 | 2589 | picomatch@2.3.1: {} 2590 | 2591 | pidtree@0.6.0: {} 2592 | 2593 | playwright-core@1.52.0: {} 2594 | 2595 | 'playwright-typescript@file:': {} 2596 | 2597 | playwright@1.52.0: 2598 | dependencies: 2599 | playwright-core: 1.52.0 2600 | optionalDependencies: 2601 | fsevents: 2.3.2 2602 | 2603 | proxy-from-env@1.1.0: {} 2604 | 2605 | qs@6.14.0: 2606 | dependencies: 2607 | side-channel: 1.1.0 2608 | 2609 | readable-stream@3.6.2: 2610 | dependencies: 2611 | inherits: 2.0.4 2612 | string_decoder: 1.3.0 2613 | util-deprecate: 1.0.2 2614 | 2615 | require-directory@2.1.1: {} 2616 | 2617 | require-main-filename@2.0.0: {} 2618 | 2619 | restore-cursor@5.1.0: 2620 | dependencies: 2621 | onetime: 7.0.0 2622 | signal-exit: 4.1.0 2623 | 2624 | rfdc@1.4.1: {} 2625 | 2626 | safe-buffer@5.2.1: {} 2627 | 2628 | safer-buffer@2.1.2: {} 2629 | 2630 | seq-queue@0.0.5: {} 2631 | 2632 | set-blocking@2.0.0: {} 2633 | 2634 | shebang-command@2.0.0: 2635 | dependencies: 2636 | shebang-regex: 3.0.0 2637 | 2638 | shebang-regex@3.0.0: {} 2639 | 2640 | side-channel-list@1.0.0: 2641 | dependencies: 2642 | es-errors: 1.3.0 2643 | object-inspect: 1.13.4 2644 | 2645 | side-channel-map@1.0.1: 2646 | dependencies: 2647 | call-bound: 1.0.4 2648 | es-errors: 1.3.0 2649 | get-intrinsic: 1.3.0 2650 | object-inspect: 1.13.4 2651 | 2652 | side-channel-weakmap@1.0.2: 2653 | dependencies: 2654 | call-bound: 1.0.4 2655 | es-errors: 1.3.0 2656 | get-intrinsic: 1.3.0 2657 | object-inspect: 1.13.4 2658 | side-channel-map: 1.0.1 2659 | 2660 | side-channel@1.1.0: 2661 | dependencies: 2662 | es-errors: 1.3.0 2663 | object-inspect: 1.13.4 2664 | side-channel-list: 1.0.0 2665 | side-channel-map: 1.0.1 2666 | side-channel-weakmap: 1.0.2 2667 | 2668 | signal-exit@4.1.0: {} 2669 | 2670 | slice-ansi@5.0.0: 2671 | dependencies: 2672 | ansi-styles: 6.2.1 2673 | is-fullwidth-code-point: 4.0.0 2674 | 2675 | slice-ansi@7.1.0: 2676 | dependencies: 2677 | ansi-styles: 6.2.1 2678 | is-fullwidth-code-point: 5.0.0 2679 | 2680 | sqlstring@2.3.3: {} 2681 | 2682 | ssf@0.11.2: 2683 | dependencies: 2684 | frac: 1.1.2 2685 | 2686 | ssh2-sftp-client@12.0.0: 2687 | dependencies: 2688 | concat-stream: 2.0.0 2689 | ssh2: 1.16.0 2690 | 2691 | ssh2@1.16.0: 2692 | dependencies: 2693 | asn1: 0.2.6 2694 | bcrypt-pbkdf: 1.0.2 2695 | optionalDependencies: 2696 | cpu-features: 0.0.10 2697 | nan: 2.22.2 2698 | 2699 | string-argv@0.3.2: {} 2700 | 2701 | string-width@4.2.3: 2702 | dependencies: 2703 | emoji-regex: 8.0.0 2704 | is-fullwidth-code-point: 3.0.0 2705 | strip-ansi: 6.0.1 2706 | 2707 | string-width@7.2.0: 2708 | dependencies: 2709 | emoji-regex: 10.3.0 2710 | get-east-asian-width: 1.2.0 2711 | strip-ansi: 7.1.0 2712 | 2713 | string_decoder@1.3.0: 2714 | dependencies: 2715 | safe-buffer: 5.2.1 2716 | 2717 | strip-ansi@6.0.1: 2718 | dependencies: 2719 | ansi-regex: 5.0.1 2720 | 2721 | strip-ansi@7.1.0: 2722 | dependencies: 2723 | ansi-regex: 6.0.1 2724 | 2725 | strip-final-newline@3.0.0: {} 2726 | 2727 | stripe@18.1.0(@types/node@22.15.3): 2728 | dependencies: 2729 | qs: 6.14.0 2730 | optionalDependencies: 2731 | '@types/node': 22.15.3 2732 | 2733 | strnum@1.1.2: {} 2734 | 2735 | supports-color@7.2.0: 2736 | dependencies: 2737 | has-flag: 4.0.0 2738 | 2739 | to-regex-range@5.0.1: 2740 | dependencies: 2741 | is-number: 7.0.0 2742 | 2743 | tslib@2.8.1: {} 2744 | 2745 | tunnel@0.0.6: {} 2746 | 2747 | tweetnacl@0.14.5: {} 2748 | 2749 | typed-rest-client@1.8.11: 2750 | dependencies: 2751 | qs: 6.14.0 2752 | tunnel: 0.0.6 2753 | underscore: 1.13.7 2754 | 2755 | typedarray@0.0.6: {} 2756 | 2757 | typescript@5.8.3: {} 2758 | 2759 | underscore@1.13.7: {} 2760 | 2761 | undici-types@5.26.5: {} 2762 | 2763 | undici-types@6.21.0: {} 2764 | 2765 | util-deprecate@1.0.2: {} 2766 | 2767 | uuid@9.0.1: {} 2768 | 2769 | which-module@2.0.1: {} 2770 | 2771 | which@2.0.2: 2772 | dependencies: 2773 | isexe: 2.0.0 2774 | 2775 | wmf@1.0.2: {} 2776 | 2777 | word@0.3.0: {} 2778 | 2779 | wrap-ansi@6.2.0: 2780 | dependencies: 2781 | ansi-styles: 4.3.0 2782 | string-width: 4.2.3 2783 | strip-ansi: 6.0.1 2784 | 2785 | wrap-ansi@9.0.0: 2786 | dependencies: 2787 | ansi-styles: 6.2.1 2788 | string-width: 7.2.0 2789 | strip-ansi: 7.1.0 2790 | 2791 | wrappy@1.0.2: {} 2792 | 2793 | xlsx@0.18.5: 2794 | dependencies: 2795 | adler-32: 1.3.1 2796 | cfb: 1.2.2 2797 | codepage: 1.15.0 2798 | crc-32: 1.2.2 2799 | ssf: 0.11.2 2800 | wmf: 1.0.2 2801 | word: 0.3.0 2802 | 2803 | y18n@4.0.3: {} 2804 | 2805 | yaml@2.7.0: {} 2806 | 2807 | yargs-parser@18.1.3: 2808 | dependencies: 2809 | camelcase: 5.3.1 2810 | decamelize: 1.2.0 2811 | 2812 | yargs@15.4.1: 2813 | dependencies: 2814 | cliui: 6.0.0 2815 | decamelize: 1.2.0 2816 | find-up: 4.1.0 2817 | get-caller-file: 2.0.5 2818 | require-directory: 2.1.1 2819 | require-main-filename: 2.0.0 2820 | set-blocking: 2.0.0 2821 | string-width: 4.2.3 2822 | which-module: 2.0.1 2823 | y18n: 4.0.3 2824 | yargs-parser: 18.1.3 2825 | 2826 | zod@3.24.3: {} 2827 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "automerge": true, 4 | "automergeType": "pr", 5 | "extends": [ 6 | "config:recommended", 7 | "group:all", 8 | "customManagers:biomeVersions" 9 | ], 10 | "pre-commit": { 11 | "enabled": true 12 | }, 13 | "schedule": ["monthly"] 14 | } 15 | -------------------------------------------------------------------------------- /resources/data.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirtal85/Playwright-Typescript-Example/ed60af014714c8b1efec4ab34bcd9c386ffaaf51/resources/data.xls -------------------------------------------------------------------------------- /resources/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirtal85/Playwright-Typescript-Example/ed60af014714c8b1efec4ab34bcd9c386ffaaf51/resources/dog.png -------------------------------------------------------------------------------- /resources/test.txt: -------------------------------------------------------------------------------- 1 | test -------------------------------------------------------------------------------- /src/entities/UserData.ts: -------------------------------------------------------------------------------- 1 | export interface UserData { 2 | user: string; 3 | password: string; 4 | error: string; 5 | description: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/fixtures/projectFixtures.ts: -------------------------------------------------------------------------------- 1 | import { test as base } from "@playwright/test"; 2 | import { HomePage } from "../pages/HomePage"; 3 | import { DatabaseService } from "../services/DatabaseService"; 4 | import { MailinatorService } from "../services/MailinatorService"; 5 | import { NetworkBlockerService } from "../services/NetworkBlockerService"; 6 | import { SecureApiService } from "../services/SecureApiService"; 7 | import { SftpService } from "../services/SftpService"; 8 | import { VisualTrackerService } from "../services/VisualTrackerService"; 9 | import { LaunchDarklyService } from "../services/launchDarkly/LaunchDarklyService"; 10 | import { S3Service } from "../services/s3Service"; 11 | import { StripeService } from "../services/stripe/StripeService"; 12 | 13 | interface Fixtures { 14 | databaseService: DatabaseService; 15 | homePage: HomePage; 16 | launchDarklyService: LaunchDarklyService; 17 | mailinatorService: MailinatorService; 18 | networkBlockerService: NetworkBlockerService; 19 | s3Service: S3Service; 20 | secureApiService: SecureApiService; 21 | sftpService: SftpService; 22 | stripeService: StripeService; 23 | visualTracker: VisualTrackerService; 24 | } 25 | 26 | export const test = base.extend({ 27 | databaseService: [ 28 | async ({}, use) => { 29 | const dbService = new DatabaseService(); 30 | await use(dbService); 31 | await dbService.closePool(); 32 | }, 33 | { scope: "test" }, 34 | ], 35 | homePage: async ({ page }, use) => { 36 | await use(new HomePage(page)); 37 | }, 38 | launchDarklyService: async ({}, use) => { 39 | const ldService = new LaunchDarklyService(); 40 | await use(ldService); 41 | }, 42 | mailinatorService: async ({}, use) => { 43 | const mailService = new MailinatorService( 44 | process.env.MAILINATOR_API_TOKEN!, 45 | process.env.MAILINATOR_DOMAIN, 46 | ); 47 | await use(mailService); 48 | }, 49 | networkBlockerService: [ 50 | async ({ page }, use) => { 51 | const networkBlocker = new NetworkBlockerService(page); 52 | const defaultBlockedUrls = [ 53 | "**/analytics.dev.example.com/**", 54 | "**/tracking.staging.example.com/**", 55 | "**/thirdparty.production.example.com/**", 56 | "**/cdn.privacy-banner.com/**", 57 | ]; 58 | await networkBlocker.blockUrls(defaultBlockedUrls); 59 | await use(networkBlocker); 60 | }, 61 | { auto: true }, 62 | ], 63 | s3Service: async ({}, use) => { 64 | const s3 = new S3Service(); 65 | await use(s3); 66 | }, 67 | secureApiService: async ({}, use) => { 68 | const service = new SecureApiService(); 69 | await use(service); 70 | }, 71 | sftpService: [ 72 | async ({}, use) => { 73 | const sftp = new SftpService(); 74 | await use(sftp); 75 | }, 76 | { scope: "test" }, 77 | ], 78 | stripeService: async ({}, use) => { 79 | const stripeService = new StripeService(process.env.STRIPE_SECRET_KEY!); 80 | await use(stripeService); 81 | }, 82 | visualTracker: async ({}, use) => { 83 | const tracker = new VisualTrackerService(); 84 | await tracker.start(); 85 | await use(tracker); 86 | await tracker.stop(); 87 | }, 88 | }); 89 | 90 | test.afterEach(async ({ request, page }, testInfo) => { 91 | if (testInfo.status !== testInfo.expectedStatus) { 92 | const response = await request.get("https://checkip.amazonaws.com"); 93 | const ip = await response.text(); 94 | await testInfo.attach("IP Address on Failure", { 95 | body: ip.trim(), 96 | contentType: "text/plain", 97 | }); 98 | const pages = page.context().pages(); 99 | for (let i = 0; i < pages.length; i++) { 100 | const currentPage = pages[i]; 101 | const url = currentPage.url(); 102 | await testInfo.attach(`URL of window ${i}`, { 103 | body: url, 104 | contentType: "text/uri-list", 105 | }); 106 | } 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /src/pages/HomePage.ts: -------------------------------------------------------------------------------- 1 | import type { Locator, Page } from "@playwright/test"; 2 | import { test } from "@playwright/test"; 3 | 4 | export class HomePage { 5 | readonly page: Page; 6 | readonly getStartedLink: Locator; 7 | 8 | constructor(page: Page) { 9 | this.page = page; 10 | this.getStartedLink = page.getByRole("link", { name: "Get started" }); 11 | } 12 | 13 | async clickGetStarted() { 14 | await test.step("Click on Get Started link", async () => { 15 | await this.getStartedLink.click(); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/services/DatabaseService.ts: -------------------------------------------------------------------------------- 1 | import * as mysql from "mysql2/promise"; 2 | 3 | export class DatabaseService { 4 | private pool: mysql.Pool; 5 | 6 | constructor() { 7 | const dbHost = process.env.DB_HOST; 8 | const dbPort = 3306; 9 | const dbUser = process.env.DB_USER; 10 | const dbPassword = process.env.DB_PASSWORD; 11 | const connectionOptions: mysql.PoolOptions = { 12 | host: dbHost, 13 | port: dbPort, 14 | user: dbUser, 15 | password: dbPassword, 16 | waitForConnections: true, 17 | connectionLimit: 10, 18 | queueLimit: 0, 19 | ssl: { 20 | minVersion: "TLSv1.2", 21 | rejectUnauthorized: false, 22 | }, 23 | }; 24 | 25 | this.pool = mysql.createPool(connectionOptions); 26 | } 27 | 28 | async executeQuery(sql: string, params: any[] = []): Promise { 29 | let connection: mysql.PoolConnection | null = null; 30 | try { 31 | connection = await this.pool.getConnection(); 32 | const [results] = await connection.query(sql, params); 33 | return results as T; 34 | } finally { 35 | if (connection) { 36 | connection.release(); 37 | } 38 | } 39 | } 40 | 41 | async closePool(): Promise { 42 | await this.pool.end(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/services/MailinatorService.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetInboxRequest, 3 | GetMessageLinksRequest, 4 | GetMessageRequest, 5 | MailinatorClient, 6 | } from "mailinator-client"; 7 | 8 | /** 9 | * A service class to interact with the Mailinator API for reading emails, 10 | * extracting links, OTPs, and more. 11 | */ 12 | export class MailinatorService { 13 | private client: MailinatorClient; 14 | private readonly domain: string; 15 | 16 | /** 17 | * Initializes the MailinatorService. 18 | * @param apiToken - The Mailinator API token. 19 | * @param domain - The domain to use (defaults to 'public'). 20 | */ 21 | constructor(apiToken: string, domain = "public") { 22 | this.client = new MailinatorClient(apiToken); 23 | this.domain = domain; 24 | } 25 | 26 | /** 27 | * Retrieves messages from a given inbox. 28 | * @param inbox - The inbox name. 29 | * @returns An array of messages in the inbox. 30 | */ 31 | async getInboxMessages(inbox: string) { 32 | const inboxRequest = new GetInboxRequest(this.domain, inbox); 33 | const response = await this.client.request(inboxRequest); 34 | return response.result?.msgs ?? []; 35 | } 36 | 37 | /** 38 | * Waits for an email with a specific subject to appear in the inbox. 39 | * @param inbox - The inbox name. 40 | * @param subject - The subject to search for. 41 | * @param index - The index of the matching email (default is 0). 42 | * @param timeoutMs - How long to wait in milliseconds (default 180000). 43 | * @param pollInterval - How often to poll in milliseconds (default 10000). 44 | * @returns The matching message object. 45 | * @throws If no matching email is found within the timeout. 46 | */ 47 | async waitForMessageBySubject( 48 | inbox: string, 49 | subject: string, 50 | index = 0, 51 | timeoutMs = 180000, 52 | pollInterval = 10000, 53 | ) { 54 | const start = Date.now(); 55 | while (Date.now() - start < timeoutMs) { 56 | const messages = await this.getInboxMessages(inbox); 57 | const matching = messages 58 | .filter((message) => message.subject === subject) 59 | .sort((a, b) => b.secondsAgo - a.secondsAgo); 60 | if (matching.length > index) { 61 | return matching[index]; 62 | } 63 | await new Promise((res) => setTimeout(res, pollInterval)); 64 | } 65 | throw new Error( 66 | `Email with subject "${subject}" not found in inbox "${inbox}"`, 67 | ); 68 | } 69 | 70 | /** 71 | * Fetches the plain text body of a message. 72 | * @param messageId - The ID of the message. 73 | * @returns The body text of the message. 74 | */ 75 | async getEmailBody(messageId: string): Promise { 76 | const message = await this.client.request( 77 | new GetMessageRequest(this.domain, messageId), 78 | ); 79 | return message.result?.parts?.[0]?.body || ""; 80 | } 81 | 82 | /** 83 | * Retrieves all hyperlinks from an email. 84 | * @param messageId - The ID of the message. 85 | * @returns An array of link URLs found in the message. 86 | */ 87 | async getLinksFromEmail(messageId: string): Promise { 88 | const linksRequest = new GetMessageLinksRequest(this.domain, messageId); 89 | const linksResponse = await this.client.request(linksRequest); 90 | return linksResponse.result?.links ?? []; 91 | } 92 | 93 | /** 94 | * Extracts a 6-digit OTP from the email body. 95 | * @param body - The body of the email. 96 | * @returns The extracted OTP. 97 | * @throws If no OTP is found. 98 | */ 99 | extractOtp(body: string): string { 100 | const match = body.match(/\b\d{6}\b/); 101 | if (!match) throw new Error("No OTP found in email body"); 102 | return match[0]; 103 | } 104 | 105 | /** 106 | * Extracts a link from the email body that contains the given partial text. 107 | * @param body - The body of the email. 108 | * @param partial - The partial string to match in the link. 109 | * @returns The full matching link. 110 | * @throws If no matching link is found. 111 | */ 112 | extractLinkByText(body: string, partial: string): string { 113 | const regex = new RegExp(`https?://[^\\s"]*${partial}[^\\s")]*`, "i"); 114 | const match = body.match(regex); 115 | if (!match) throw new Error(`Link containing "${partial}" not found`); 116 | return match[0].replace(/&/g, "&"); 117 | } 118 | 119 | /** 120 | * Extracts a link near some surrounding text in the email body. 121 | * @param body - The body of the email. 122 | * @param surroundingText - The text near the desired link. 123 | * @returns The extracted link. 124 | * @throws If no link is found near the given text. 125 | */ 126 | extractLinkBySurroundingText(body: string, surroundingText: string): string { 127 | const start = body.indexOf(surroundingText); 128 | if (start === -1) throw new Error("Surrounding text not found"); 129 | const substring = body.substring(start); 130 | const match = substring.match(/\((https?:\/\/[^\s)]+)\)/); 131 | if (!match) throw new Error("No link found near surrounding text"); 132 | return match[1].replace(/&/g, "&"); 133 | } 134 | 135 | /** 136 | * Waits for an OTP email and extracts the OTP code. 137 | * @param inbox - The inbox to check. 138 | * @param subject - The expected email subject. 139 | * @returns The extracted OTP. 140 | */ 141 | async waitForOtp(inbox: string, subject: string): Promise { 142 | const message = await this.waitForMessageBySubject(inbox, subject); 143 | const body = await this.getEmailBody(message.id); 144 | return this.extractOtp(body); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/services/NetworkBlockerService.ts: -------------------------------------------------------------------------------- 1 | import type { Page, Route } from "@playwright/test"; 2 | 3 | /** 4 | * Service to handle blocking network requests using Playwright's page.route(). 5 | * The Page object is provided during instantiation. 6 | */ 7 | export class NetworkBlockerService { 8 | private readonly page: Page; 9 | private readonly routeHandlers = new Map< 10 | string, 11 | (route: Route) => Promise 12 | >(); 13 | 14 | constructor(page: Page) { 15 | this.page = page; 16 | } 17 | 18 | /** 19 | * Applies routing rules to block network requests matching the provided URL patterns. 20 | */ 21 | async blockUrls(urlsToBlock: string[]): Promise { 22 | for (const urlPattern of urlsToBlock) { 23 | const handler = async (route: Route) => { 24 | await route.abort(); 25 | }; 26 | this.routeHandlers.set(urlPattern, handler); 27 | await this.page.route(urlPattern, handler); 28 | } 29 | } 30 | 31 | /** 32 | * Removes all routing rules previously set on the page. 33 | */ 34 | async unblockAll(): Promise { 35 | for (const [pattern, handler] of this.routeHandlers.entries()) { 36 | await this.page.unroute(pattern, handler); 37 | } 38 | this.routeHandlers.clear(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/services/SecureApiService.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "node:fs"; 2 | import { Agent } from "node:https"; 3 | import path from "node:path"; 4 | import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; 5 | import axios from "axios"; 6 | 7 | /** 8 | * A generic payload interface for secure POST requests. 9 | * Replace or extend this with domain-specific fields if needed. 10 | */ 11 | export interface SecurePostPayload { 12 | [key: string]: any; 13 | } 14 | 15 | /** 16 | * A simple, secure API service using mutual TLS (mTLS) authentication with Axios. 17 | */ 18 | export class SecureApiService { 19 | private readonly axiosInstance: AxiosInstance; 20 | 21 | constructor() { 22 | const cert = readFileSync(path.resolve("resources/certificate/cert.pem")); 23 | const key = readFileSync( 24 | path.resolve("resources/certificate/private-key.pem"), 25 | ); 26 | const httpsAgent = new Agent({ 27 | cert, 28 | key, 29 | rejectUnauthorized: true, 30 | }); 31 | const config: AxiosRequestConfig = { 32 | httpsAgent, 33 | headers: { 34 | Accept: "application/json", 35 | "Content-Type": "application/json", 36 | Authorization: `Bearer ${process.env.BEARER}`, 37 | }, 38 | }; 39 | this.axiosInstance = axios.create(config); 40 | this.axiosInstance.interceptors.response.use( 41 | (res: AxiosResponse) => res, 42 | (err: any) => { 43 | return Promise.reject(err); 44 | }, 45 | ); 46 | } 47 | 48 | /** 49 | * Sends a secure POST request with client certificate authentication. 50 | @param url Full URL to the endpoint (e.g., 'https://api.example.com/v1/resource') 51 | @param payload Request body data 52 | @param config Optional Axios request configuration 53 | */ 54 | async post( 55 | url: string, 56 | payload: SecurePostPayload, 57 | config?: AxiosRequestConfig, 58 | ): Promise> { 59 | return this.axiosInstance.post(url, payload, config); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/services/SftpService.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import SftpClient from "ssh2-sftp-client"; 3 | 4 | /** Local directory containing resources to upload */ 5 | const RESOURCES_DIR = path.resolve(process.cwd(), "resources"); 6 | /** Connection timeout in milliseconds */ 7 | const CONNECT_TIMEOUT_MS = 3 * 60 * 1000; 8 | 9 | /** 10 | * SFTP service for uploading and querying files. 11 | * The default port is 22 12 | */ 13 | export class SftpService { 14 | private readonly config: SftpClient.ConnectOptions; 15 | 16 | constructor() { 17 | const host = process.env.SFTP_HOST; 18 | const username = process.env.SFTP_USER; 19 | const password = process.env.SFTP_PASSWORD; 20 | this.config = { 21 | host, 22 | username, 23 | password, 24 | readyTimeout: CONNECT_TIMEOUT_MS, 25 | }; 26 | } 27 | 28 | /** 29 | * Uploads a specified local file from the resources directory to a specified remote directory via SFTP. 30 | * 31 | * @param {string} localFileName The name of the file within the local 'resources' directory to upload. 32 | * @param {string} [remoteDir="/outbound/upload"] The remote directory to upload the file to. 33 | * @returns {Promise} A promise that resolves when the upload is complete. 34 | * @throws Will propagate any error thrown by the SFTP client 35 | * (e.g., connection issues, file not found locally, permissions). 36 | */ 37 | async uploadFile( 38 | localFileName: string, 39 | remoteDir = "/remote/upload", 40 | ): Promise { 41 | const localFilePath = path.join(RESOURCES_DIR, localFileName); 42 | const remoteFilePath = path.posix.join(remoteDir, localFileName); 43 | await this.performSftpAction(async (client) => { 44 | await client.put(localFilePath, remoteFilePath); 45 | }); 46 | } 47 | 48 | /** 49 | * Counts how many regular files in the remote directory start with `${filePrefix}-`. 50 | * This excludes directories and symlinks, ensuring only files are counted. 51 | * 52 | * @param {string} filePrefix - The prefix (e.g., user.claimId) to match filenames. 53 | * @param {string} [remoteDir="/remote/inbox"] - The remote directory to search in. 54 | * @returns {Promise} - The number of matching regular files. 55 | */ 56 | async countFilesWithPrefix( 57 | filePrefix: string, 58 | remoteDir = "/remote/inbox", 59 | ): Promise { 60 | return await this.performSftpAction(async (client) => { 61 | const fileList = await client.list(remoteDir); 62 | return fileList.filter( 63 | (file) => file.type === "-" && file.name.startsWith(`${filePrefix}-`), 64 | ).length; 65 | }); 66 | } 67 | 68 | /** 69 | * Performs a specified SFTP action using a connected SFTP client. 70 | * 71 | * @template T - The type of the result returned by the action. 72 | * @param {(client: SftpClient) => Promise} action - The action to perform, which receives an SFTP client and returns a promise. 73 | * @returns {Promise} - A promise that resolves with the result of the action. 74 | * @throws Will propagate any error thrown by the action or connection process. 75 | */ 76 | private async performSftpAction( 77 | action: (client: SftpClient) => Promise, 78 | ): Promise { 79 | const client = new SftpClient(); 80 | try { 81 | await client.connect(this.config); 82 | return await action(client); 83 | } finally { 84 | await client.end(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/services/VisualTrackerService.ts: -------------------------------------------------------------------------------- 1 | import type { ElementHandle, Page } from "@playwright/test"; 2 | import { chromium, test } from "@playwright/test"; 3 | import { PlaywrightVisualRegressionTracker } from "@visual-regression-tracker/agent-playwright"; 4 | import type { IgnoreArea } from "@visual-regression-tracker/sdk-js"; 5 | import { DIFF_TOLERANCE_PERCENT } from "../utilities/constants"; 6 | 7 | /** 8 | * Options used to configure a visual regression tracking action. 9 | */ 10 | interface TrackOptions { 11 | /** A list of elements to be ignored in the screenshot comparison */ 12 | ignoreElements?: ElementHandle[]; 13 | /** Visual difference tolerance as a percentage (0–100) */ 14 | diffTolerancePercent?: number; 15 | /** Optional comment to include with the test run */ 16 | comment?: string; 17 | /** Whether to capture the full page. Defaults to true */ 18 | fullPage?: boolean; 19 | } 20 | 21 | /** 22 | * A service class that wraps the Visual Regression Tracker Playwright agent 23 | * and provides utility methods for tracking pages and elements with 24 | * advanced options like ignore areas and diff tolerance. 25 | * 26 | * By default, all elements on the page are ignored during visual comparison 27 | * to avoid prefilled values affecting the results. 28 | */ 29 | export class VisualTrackerService { 30 | private vrt: PlaywrightVisualRegressionTracker; 31 | 32 | /** 33 | * Creates a new instance of the VisualTrackerService. 34 | */ 35 | constructor() { 36 | this.vrt = new PlaywrightVisualRegressionTracker(chromium.name()); 37 | } 38 | 39 | /** 40 | * Starts the Visual Regression Tracker client session. 41 | * This must be called before any `trackPage` or `trackElement` calls. 42 | */ 43 | async start() { 44 | await this.vrt.start(); 45 | } 46 | 47 | /** 48 | * Stops the Visual Regression Tracker client session. 49 | * This should be called at the end of your test suite. 50 | */ 51 | async stop() { 52 | await this.vrt.stop(); 53 | } 54 | 55 | /** 56 | * Tracks a screenshot of the full page or current viewport for visual comparison. 57 | * 58 | * @param page The Playwright page object to capture. 59 | * @param baseLineName Optional name to use for the baseline image. 60 | * If omitted, the test title is used. 61 | * @param options Optional tracking options: 62 | * - ignoreElements: Elements to ignore during comparison. 63 | * - diffTolerancePercent: Allowed visual difference threshold. 64 | * - comment: A comment for the test run. 65 | * - fullPage: Whether to capture the full page. 66 | * Defaults to true. 67 | */ 68 | async trackPage( 69 | page: Page, 70 | baseLineName?: string, 71 | options: TrackOptions = {}, 72 | ) { 73 | const { 74 | ignoreElements = [], 75 | diffTolerancePercent = DIFF_TOLERANCE_PERCENT, 76 | comment, 77 | fullPage = true, 78 | } = options; 79 | 80 | const name = baseLineName ?? test.info().title; 81 | const ignoreAreas: IgnoreArea[] = await this.getIgnoreAreas( 82 | page, 83 | ignoreElements, 84 | ); 85 | await this.vrt.trackPage(page, name, { 86 | diffTollerancePercent: diffTolerancePercent, 87 | ignoreAreas, 88 | comment, 89 | screenshotOptions: { fullPage }, 90 | }); 91 | } 92 | 93 | /** 94 | * Tracks a screenshot of a specific element for visual comparison. 95 | * 96 | * @param page The Playwright page object to capture. 97 | * @param elementHandle The Playwright ElementHandle to capture. 98 | * @param baseLineName Optional name to use for the baseline image. If omitted, the test title is used. 99 | * @param options Optional tracking options: 100 | * - ignoreElements: Additional elements to ignore. 101 | * - diffTolerancePercent: Allowed visual difference threshold. 102 | * - comment: A comment for the test run. 103 | */ 104 | async trackElement( 105 | page: Page, 106 | elementHandle: ElementHandle, 107 | baseLineName?: string, 108 | options: TrackOptions = {}, 109 | ) { 110 | const { 111 | ignoreElements = [], 112 | diffTolerancePercent = DIFF_TOLERANCE_PERCENT, 113 | comment, 114 | } = options; 115 | const name = baseLineName ?? test.info().title; 116 | const ignoreAreas: IgnoreArea[] = await this.getIgnoreAreas( 117 | page, 118 | ignoreElements, 119 | ); 120 | await this.vrt.trackElementHandle(elementHandle, name, { 121 | diffTollerancePercent: diffTolerancePercent, 122 | ignoreAreas, 123 | comment, 124 | }); 125 | } 126 | 127 | /** 128 | * Converts a list of ElementHandles into VRT-compatible ignore areas (bounding boxes). 129 | * Includes all elements on the page by default, even if not explicitly passed in. 130 | * 131 | * @param page The Playwright page object. 132 | * @param elements A list of elements to additionally ignore. 133 | * @returns A list of ignore area objects for the VRT API. 134 | */ 135 | private async getIgnoreAreas( 136 | page: Page, 137 | elements: ElementHandle[] = [], 138 | ): Promise { 139 | const inputElements = await page.$$("input"); 140 | const allElements = [...elements, ...inputElements]; 141 | return Promise.all( 142 | allElements.map(async (elementHandle) => { 143 | const box = await elementHandle.boundingBox(); 144 | if (!box) return null; 145 | return { 146 | x: Math.floor(box.x), 147 | y: Math.floor(box.y), 148 | width: Math.ceil(box.width), 149 | height: Math.ceil(box.height), 150 | }; 151 | }), 152 | ).then((areas) => areas.filter((area): area is IgnoreArea => !!area)); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/services/launchDarkly/LaunchDarklyService.ts: -------------------------------------------------------------------------------- 1 | import { test } from "@playwright/test"; 2 | import axios from "axios"; 3 | import { z } from "zod"; 4 | 5 | const LdFlagResponseSchema = z.object({ 6 | environments: z.record(z.string(), z.object({ on: z.boolean() })), 7 | }); 8 | 9 | const ldApi = axios.create({ 10 | baseURL: "https://app.launchdarkly.com/api/v2", 11 | headers: { 12 | Authorization: process.env.LD_TOKEN, 13 | "Content-Type": "application/json", 14 | }, 15 | }); 16 | 17 | export class LaunchDarklyService { 18 | /** 19 | * Returns the status of the feature flag identified by `flagKey` in the environment 20 | * identified by `environmentKey`. 21 | * 22 | * @param flagKey The key identifying the feature flag in LaunchDarkly. 23 | * @param environmentKey The environment in which to check the feature flag's status. 24 | * Default to the value of the `LD_ENV` environment variable, or `'test'` if that 25 | * variable is unset. 26 | * @returns The status of the feature flag, or `false` if there is no such flag in 27 | * the given environment. 28 | */ 29 | async getFlagStatus( 30 | flagKey: string, 31 | environmentKey: string = process.env.LD_ENV || "test", 32 | ): Promise { 33 | const response = await ldApi.get(`/flags/default/${flagKey}`); 34 | const parsed = LdFlagResponseSchema.parse(response.data); 35 | return parsed.environments[environmentKey]?.on ?? false; 36 | } 37 | 38 | async skipTestUnlessFlagStatusIs( 39 | flagKey: string, 40 | expectedStatus: boolean, 41 | environmentKey: string = process.env.LD_ENV || "test", 42 | ): Promise { 43 | const actualStatus = await this.getFlagStatus(flagKey, environmentKey); 44 | test.skip( 45 | actualStatus !== expectedStatus, 46 | `Skipping test: flag '${flagKey}' in '${environmentKey}' is ${actualStatus}, expected ${expectedStatus}`, 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/services/launchDarkly/featureFlags.ts: -------------------------------------------------------------------------------- 1 | export const FeatureFlags = { 2 | EXAMPLE_FEATURE_A: "example-feature-a", 3 | } as const; 4 | 5 | export type FeatureFlagValue = (typeof FeatureFlags)[keyof typeof FeatureFlags]; 6 | -------------------------------------------------------------------------------- /src/services/s3Service.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs"; 2 | import { 3 | PutObjectCommand, 4 | type PutObjectCommandOutput, 5 | S3Client, 6 | type S3ClientConfig, 7 | } from "@aws-sdk/client-s3"; 8 | import { fromIni } from "@aws-sdk/credential-provider-ini"; 9 | 10 | /** 11 | * Service for interacting with AWS S3. 12 | */ 13 | export class S3Service { 14 | private readonly s3Client: S3Client; 15 | 16 | /** 17 | * Initializes the S3 client. 18 | * @param [region='us-east-1'] - The AWS region to connect to. 19 | */ 20 | constructor(region = "us-east-1") { 21 | const clientConfig: S3ClientConfig = { 22 | region, 23 | credentials: fromIni({ profile: process.env.ENVIRONMENT }), 24 | }; 25 | this.s3Client = new S3Client(clientConfig); 26 | } 27 | 28 | /** 29 | * Uploads a local file to an S3 bucket. 30 | * 31 | * @param {string} bucketName - The name of the target S3 bucket. 32 | * @param {string} key - The key (path including filename) for the object in the bucket. 33 | * @param {string} filePath - The local path to the file to upload. 34 | * @returns {Promise} A promise that resolves with the result of the PutObjectCommand. 35 | * @throws {Error} Throws an error if the upload fails. 36 | */ 37 | async uploadFile( 38 | bucketName: string, 39 | key: string, 40 | filePath: string, 41 | ): Promise { 42 | const fileStream = fs.createReadStream(filePath); 43 | const uploadParams = { 44 | Bucket: bucketName, 45 | Key: key, 46 | Body: fileStream, 47 | }; 48 | const command = new PutObjectCommand(uploadParams); 49 | return await this.s3Client.send(command); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/services/stripe/PaymentMethod.ts: -------------------------------------------------------------------------------- 1 | export type CardType = 2 | | "visa" 3 | | "masterCard" 4 | | "insufficientFunds" 5 | | "declineAfterAttaching"; 6 | 7 | export interface CreditCardDetails { 8 | cardHolderName: string; 9 | cardNumber: string; 10 | cvv: string; 11 | expirationDate: string; 12 | token: string; 13 | zipCode: string; 14 | } 15 | 16 | /** Credit‑card test fixtures keyed by card type */ 17 | const CREDIT_CARDS: Record = { 18 | declineAfterAttaching: { 19 | cardHolderName: "Test Automation", 20 | cardNumber: "4000000000000341", 21 | cvv: "123", 22 | expirationDate: "12/28", 23 | token: "tok_chargeCustomerFail", 24 | zipCode: "12345", 25 | }, 26 | insufficientFunds: { 27 | cardHolderName: "Test Automation", 28 | cardNumber: "4000000000009995", 29 | cvv: "123", 30 | expirationDate: "12/28", 31 | token: "tok_visa_chargeDeclinedInsufficientFunds", 32 | zipCode: "12345", 33 | }, 34 | masterCard: { 35 | cardHolderName: "Test Automation", 36 | cardNumber: "5555555555554444", 37 | cvv: "123", 38 | expirationDate: "06/29", 39 | token: "tok_mastercard", 40 | zipCode: "12345", 41 | }, 42 | visa: { 43 | cardHolderName: "Test Automation", 44 | cardNumber: "4242424242424242", 45 | cvv: "123", 46 | expirationDate: "12/28", 47 | token: "tok_visa", 48 | zipCode: "12345", 49 | }, 50 | }; 51 | export const VISA = "visa" satisfies CardType; 52 | export const MASTERCARD = "masterCard" satisfies CardType; 53 | export const INSUFFICIENT_FUNDS = "insufficientFunds" satisfies CardType; 54 | export const DECLINE_AFTER_ATTACHING = 55 | "declineAfterAttaching" satisfies CardType; 56 | 57 | export function getCreditCard(cardType: CardType): CreditCardDetails { 58 | return CREDIT_CARDS[cardType]; 59 | } 60 | -------------------------------------------------------------------------------- /src/services/stripe/StripeService.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | // stripe-service.ts 3 | import Stripe from "stripe"; 4 | import { type CardType, getCreditCard } from "./PaymentMethod"; 5 | 6 | export class StripeService { 7 | private readonly stripe: Stripe; 8 | 9 | constructor(apiKey: string) { 10 | this.stripe = new Stripe(apiKey); 11 | } 12 | 13 | /* Retrieve all invoices for the customer and pay every unpaid invoice */ 14 | async payUnpaidInvoices(customerId: string): Promise { 15 | if (!customerId) throw new Error("Customer ID is required"); 16 | const { data: invoices } = await this.stripe.invoices.list({ 17 | customer: customerId, 18 | status: "open", 19 | }); 20 | if (invoices.length === 0) throw new Error("No unpaid invoices found"); 21 | await Promise.all(invoices.map((inv) => this.stripe.invoices.pay(inv.id))); 22 | } 23 | 24 | /* Stamp a service‑billing metadata block on an invoice */ 25 | async setMetadataForServiceInvoice(invoiceId: string): Promise { 26 | if (!invoiceId) throw new Error("Invoice ID is required"); 27 | await this.stripe.invoices.update(invoiceId, { 28 | metadata: { 29 | serviceType: "Premium", 30 | serviceDate: "2024‑12‑31", 31 | serviceCategory: "Consulting", 32 | serviceDuration: "60 minutes", 33 | serviceLocation: "Remote", 34 | serviceNotes: "Initial consultation session", 35 | }, 36 | }); 37 | } 38 | 39 | /* Create a new invoice and seed it with one invoice‑item */ 40 | async createInvoice(customerId: string): Promise { 41 | if (!customerId) throw new Error("Customer ID is required"); 42 | const invoice = await this.stripe.invoices.create({ 43 | customer: customerId, 44 | auto_advance: true, 45 | collection_method: "send_invoice", 46 | days_until_due: 30, 47 | }); 48 | if (!invoice.id) throw new Error("Failed to create invoice"); 49 | await this.stripe.invoiceItems.create({ 50 | customer: customerId, 51 | price_data: { 52 | currency: "usd", 53 | product: "prod_test", 54 | unit_amount: 1_000, 55 | }, 56 | description: "Premium service package", 57 | invoice: invoice.id, 58 | }); 59 | return invoice.id; 60 | } 61 | 62 | /* Detach the customer’s first payment‑method (if any) */ 63 | async detachPaymentMethod(customerId: string): Promise { 64 | if (!customerId) throw new Error("Customer ID is required"); 65 | const { data } = await this.stripe.paymentMethods.list({ 66 | customer: customerId, 67 | }); 68 | if (data[0]?.id) await this.stripe.paymentMethods.detach(data[0].id); 69 | } 70 | 71 | /* Attach a card test‑fixture to the customer (non‑default) */ 72 | async attachCard(cardType: CardType, customerId: string): Promise { 73 | if (!customerId) throw new Error("Customer ID is required"); 74 | const token = getCreditCard(cardType).token; 75 | const pm = await this.stripe.paymentMethods.create({ 76 | type: "card", 77 | card: { token }, 78 | }); 79 | await this.stripe.paymentMethods.attach(pm.id, { customer: customerId }); 80 | } 81 | 82 | /* Back‑date an arbitrary metadata field by N days */ 83 | async updateInvoiceMetadata( 84 | invoiceId: string, 85 | field: string, 86 | daysBack: number, 87 | ): Promise { 88 | if (!invoiceId) throw new Error("Invoice ID is required"); 89 | const date = new Date(); 90 | date.setDate(date.getDate() - daysBack); 91 | const yyyyMmDd = date.toISOString().split("T")[0]; 92 | await this.stripe.invoices.update(invoiceId, { 93 | metadata: { [field]: yyyyMmDd }, 94 | }); 95 | } 96 | 97 | /* Attach a card and set it as the customer’s default */ 98 | async attachCreditCardAsDefault( 99 | cardType: CardType, 100 | customerId: string, 101 | ): Promise { 102 | if (!customerId) throw new Error("Customer ID is required"); 103 | const token = getCreditCard(cardType).token; 104 | const paymentMethod = await this.stripe.paymentMethods.create({ 105 | type: "card", 106 | card: { token }, 107 | }); 108 | await this.stripe.paymentMethods.attach(paymentMethod.id, { 109 | customer: customerId, 110 | }); 111 | await this.stripe.customers.update(customerId, { 112 | invoice_settings: { default_payment_method: paymentMethod.id }, 113 | }); 114 | } 115 | 116 | /* Assert a Stripe invoice’s status */ 117 | async verifyInvoiceStatus( 118 | invoiceId: string, 119 | expected: Stripe.Invoice.Status, 120 | ): Promise { 121 | if (!invoiceId) throw new Error("Invoice ID is required"); 122 | const invoice = await this.stripe.invoices.retrieve(invoiceId); 123 | expect(invoice.status).toBe(expected); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/utilities/constants.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join } from "node:path"; 2 | import { fileURLToPath } from "node:url"; 3 | 4 | const __filename = fileURLToPath(import.meta.url); 5 | const __dirname = dirname(__filename); 6 | 7 | export const DIFF_TOLERANCE_PERCENT = 0.01; 8 | export const AUTOMATION_USER_AGENT = "automation"; 9 | export const DATA_PATH = join(__dirname, "../../resources"); 10 | -------------------------------------------------------------------------------- /src/utilities/environment.ts: -------------------------------------------------------------------------------- 1 | import { test } from "@playwright/test"; 2 | 3 | /** 4 | * Defines constants for known execution environments. 5 | */ 6 | export const Environment = { 7 | DEV: "DEV", 8 | CANARY: "CANARY", 9 | PROD: "PROD", 10 | } as const; 11 | 12 | /** 13 | * Type alias for the string literal values of the environments. 14 | * Useful for type-checking function parameters. 15 | */ 16 | export type EnvironmentValue = (typeof Environment)[keyof typeof Environment]; 17 | const currentEnv = ( 18 | process.env.DOMAIN || Environment.DEV 19 | ).toLowerCase() as EnvironmentValue; 20 | 21 | /** 22 | * Checks if the current execution environment matches the specified environment. 23 | * Reads the current environment from the `DOMAIN` environment variable (defaults to 'DEV'). 24 | * Comparison is case-insensitive. 25 | * 26 | * @param {EnvironmentValue} targetEnvironment The environment to check against (e.g., Environments.DEV). 27 | * @returns {boolean} True if the current environment matches the target, false otherwise. 28 | */ 29 | export function isEnvironment(targetEnvironment: EnvironmentValue): boolean { 30 | return currentEnv === targetEnvironment.toLowerCase(); 31 | } 32 | 33 | /** 34 | * Skips the current Playwright test unless the execution environment matches the specified environment. 35 | * Must be called early within a Playwright test function. 36 | * 37 | * @param {EnvironmentValue} requiredEnvironment The environment the test is required to run in. 38 | */ 39 | export function skipTestUnlessEnvironmentIs( 40 | requiredEnvironment: EnvironmentValue, 41 | ): void { 42 | const shouldSkip = !isEnvironment(requiredEnvironment); 43 | test.skip( 44 | shouldSkip, 45 | `Skipping test because current environment '${currentEnv}' is not the required environment '${requiredEnvironment.toLowerCase()}'.`, 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/utilities/fileUtils.ts: -------------------------------------------------------------------------------- 1 | import type { WorkBook, WorkSheet } from "xlsx"; 2 | import xlsx from "xlsx"; 3 | import type { UserData } from "../entities/UserData"; 4 | 5 | /** 6 | * Read an Excel file and return its rows as structured objects. 7 | */ 8 | export async function readExcelFile(filePath: string): Promise { 9 | const workbook: WorkBook = xlsx.readFile(filePath); 10 | return workbook.SheetNames.flatMap((name) => 11 | xlsx.utils.sheet_to_json(workbook.Sheets[name] as WorkSheet), 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /tests/demo-todo-app.spec.ts: -------------------------------------------------------------------------------- 1 | import { type Page, expect, test } from "@playwright/test"; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto("https://demo.playwright.dev/todomvc"); 5 | }); 6 | 7 | const TODO_ITEMS = [ 8 | "buy some cheese", 9 | "feed the cat", 10 | "book a doctors appointment", 11 | ] as const; 12 | 13 | test.describe("New Todo", () => { 14 | test("should allow me to add todo items", async ({ page }) => { 15 | // create a new todo locator 16 | const newTodo = page.getByPlaceholder("What needs to be done?"); 17 | 18 | // Create 1st todo. 19 | await newTodo.fill(TODO_ITEMS[0]); 20 | await newTodo.press("Enter"); 21 | 22 | // Make sure the list only has one todo item. 23 | await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]); 24 | 25 | // Create 2nd todo. 26 | await newTodo.fill(TODO_ITEMS[1]); 27 | await newTodo.press("Enter"); 28 | 29 | // Make sure the list now has two todo items. 30 | await expect(page.getByTestId("todo-title")).toHaveText([ 31 | TODO_ITEMS[0], 32 | TODO_ITEMS[1], 33 | ]); 34 | 35 | await checkNumberOfTodosInLocalStorage(page, 2); 36 | }); 37 | 38 | test("should clear text input field when an item is added", async ({ 39 | page, 40 | }) => { 41 | // create a new todo locator 42 | const newTodo = page.getByPlaceholder("What needs to be done?"); 43 | 44 | // Create one todo item. 45 | await newTodo.fill(TODO_ITEMS[0]); 46 | await newTodo.press("Enter"); 47 | 48 | // Check that input is empty. 49 | await expect(newTodo).toBeEmpty(); 50 | await checkNumberOfTodosInLocalStorage(page, 1); 51 | }); 52 | 53 | test("should append new items to the bottom of the list", async ({ 54 | page, 55 | }) => { 56 | // Create 3 items. 57 | await createDefaultTodos(page); 58 | 59 | // create a todo count locator 60 | const todoCount = page.getByTestId("todo-count"); 61 | 62 | // Check test using different methods. 63 | await expect(page.getByText("3 items left")).toBeVisible(); 64 | await expect(todoCount).toHaveText("3 items left"); 65 | await expect(todoCount).toContainText("3"); 66 | await expect(todoCount).toHaveText(/3/); 67 | 68 | // Check all items in one call. 69 | await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS); 70 | await checkNumberOfTodosInLocalStorage(page, 3); 71 | }); 72 | }); 73 | 74 | test.describe("Mark all as completed", () => { 75 | test.beforeEach(async ({ page }) => { 76 | await createDefaultTodos(page); 77 | await checkNumberOfTodosInLocalStorage(page, 3); 78 | }); 79 | 80 | test.afterEach(async ({ page }) => { 81 | await checkNumberOfTodosInLocalStorage(page, 3); 82 | }); 83 | 84 | test("should allow me to mark all items as completed", async ({ page }) => { 85 | // Complete all todos. 86 | await page.getByLabel("Mark all as complete").check(); 87 | 88 | // Ensure all todos have 'completed' class. 89 | await expect(page.getByTestId("todo-item")).toHaveClass([ 90 | "completed", 91 | "completed", 92 | "completed", 93 | ]); 94 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 95 | }); 96 | 97 | test("should allow me to clear the complete state of all items", async ({ 98 | page, 99 | }) => { 100 | const toggleAll = page.getByLabel("Mark all as complete"); 101 | // Check and then immediately uncheck. 102 | await toggleAll.check(); 103 | await toggleAll.uncheck(); 104 | 105 | // Should be no completed classes. 106 | await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]); 107 | }); 108 | 109 | test("complete all checkbox should update state when items are completed / cleared", async ({ 110 | page, 111 | }) => { 112 | const toggleAll = page.getByLabel("Mark all as complete"); 113 | await toggleAll.check(); 114 | await expect(toggleAll).toBeChecked(); 115 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 116 | 117 | // Uncheck first todo. 118 | const firstTodo = page.getByTestId("todo-item").nth(0); 119 | await firstTodo.getByRole("checkbox").uncheck(); 120 | 121 | // Reuse toggleAll locator and make sure its not checked. 122 | await expect(toggleAll).not.toBeChecked(); 123 | 124 | await firstTodo.getByRole("checkbox").check(); 125 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 126 | 127 | // Assert the toggle all is checked again. 128 | await expect(toggleAll).toBeChecked(); 129 | }); 130 | }); 131 | 132 | test.describe("Item", () => { 133 | test("should allow me to mark items as complete", async ({ page }) => { 134 | // create a new todo locator 135 | const newTodo = page.getByPlaceholder("What needs to be done?"); 136 | 137 | // Create two items. 138 | for (const item of TODO_ITEMS.slice(0, 2)) { 139 | await newTodo.fill(item); 140 | await newTodo.press("Enter"); 141 | } 142 | 143 | // Check first item. 144 | const firstTodo = page.getByTestId("todo-item").nth(0); 145 | await firstTodo.getByRole("checkbox").check(); 146 | await expect(firstTodo).toHaveClass("completed"); 147 | 148 | // Check second item. 149 | const secondTodo = page.getByTestId("todo-item").nth(1); 150 | await expect(secondTodo).not.toHaveClass("completed"); 151 | await secondTodo.getByRole("checkbox").check(); 152 | 153 | // Assert completed class. 154 | await expect(firstTodo).toHaveClass("completed"); 155 | await expect(secondTodo).toHaveClass("completed"); 156 | }); 157 | 158 | test("should allow me to un-mark items as complete", async ({ page }) => { 159 | // create a new todo locator 160 | const newTodo = page.getByPlaceholder("What needs to be done?"); 161 | 162 | // Create two items. 163 | for (const item of TODO_ITEMS.slice(0, 2)) { 164 | await newTodo.fill(item); 165 | await newTodo.press("Enter"); 166 | } 167 | 168 | const firstTodo = page.getByTestId("todo-item").nth(0); 169 | const secondTodo = page.getByTestId("todo-item").nth(1); 170 | const firstTodoCheckbox = firstTodo.getByRole("checkbox"); 171 | 172 | await firstTodoCheckbox.check(); 173 | await expect(firstTodo).toHaveClass("completed"); 174 | await expect(secondTodo).not.toHaveClass("completed"); 175 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 176 | 177 | await firstTodoCheckbox.uncheck(); 178 | await expect(firstTodo).not.toHaveClass("completed"); 179 | await expect(secondTodo).not.toHaveClass("completed"); 180 | await checkNumberOfCompletedTodosInLocalStorage(page, 0); 181 | }); 182 | 183 | test("should allow me to edit an item", async ({ page }) => { 184 | await createDefaultTodos(page); 185 | 186 | const todoItems = page.getByTestId("todo-item"); 187 | const secondTodo = todoItems.nth(1); 188 | await secondTodo.dblclick(); 189 | await expect(secondTodo.getByRole("textbox", { name: "Edit" })).toHaveValue( 190 | TODO_ITEMS[1], 191 | ); 192 | await secondTodo 193 | .getByRole("textbox", { name: "Edit" }) 194 | .fill("buy some sausages"); 195 | await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter"); 196 | 197 | // Explicitly assert the new text value. 198 | await expect(todoItems).toHaveText([ 199 | TODO_ITEMS[0], 200 | "buy some sausages", 201 | TODO_ITEMS[2], 202 | ]); 203 | await checkTodosInLocalStorage(page, "buy some sausages"); 204 | }); 205 | }); 206 | 207 | test.describe("Editing", () => { 208 | test.beforeEach(async ({ page }) => { 209 | await createDefaultTodos(page); 210 | await checkNumberOfTodosInLocalStorage(page, 3); 211 | }); 212 | 213 | test("should hide other controls when editing", async ({ page }) => { 214 | const todoItem = page.getByTestId("todo-item").nth(1); 215 | await todoItem.dblclick(); 216 | await expect(todoItem.getByRole("checkbox")).toBeHidden(); 217 | await expect( 218 | todoItem.locator("label", { 219 | hasText: TODO_ITEMS[1], 220 | }), 221 | ).toBeHidden(); 222 | await checkNumberOfTodosInLocalStorage(page, 3); 223 | }); 224 | 225 | test("should save edits on blur", async ({ page }) => { 226 | const todoItems = page.getByTestId("todo-item"); 227 | await todoItems.nth(1).dblclick(); 228 | await todoItems 229 | .nth(1) 230 | .getByRole("textbox", { name: "Edit" }) 231 | .fill("buy some sausages"); 232 | await todoItems 233 | .nth(1) 234 | .getByRole("textbox", { name: "Edit" }) 235 | .dispatchEvent("blur"); 236 | 237 | await expect(todoItems).toHaveText([ 238 | TODO_ITEMS[0], 239 | "buy some sausages", 240 | TODO_ITEMS[2], 241 | ]); 242 | await checkTodosInLocalStorage(page, "buy some sausages"); 243 | }); 244 | 245 | test("should trim entered text", async ({ page }) => { 246 | const todoItems = page.getByTestId("todo-item"); 247 | await todoItems.nth(1).dblclick(); 248 | await todoItems 249 | .nth(1) 250 | .getByRole("textbox", { name: "Edit" }) 251 | .fill(" buy some sausages "); 252 | await todoItems 253 | .nth(1) 254 | .getByRole("textbox", { name: "Edit" }) 255 | .press("Enter"); 256 | 257 | await expect(todoItems).toHaveText([ 258 | TODO_ITEMS[0], 259 | "buy some sausages", 260 | TODO_ITEMS[2], 261 | ]); 262 | await checkTodosInLocalStorage(page, "buy some sausages"); 263 | }); 264 | 265 | test("should remove the item if an empty text string was entered", async ({ 266 | page, 267 | }) => { 268 | const todoItems = page.getByTestId("todo-item"); 269 | await todoItems.nth(1).dblclick(); 270 | await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill(""); 271 | await todoItems 272 | .nth(1) 273 | .getByRole("textbox", { name: "Edit" }) 274 | .press("Enter"); 275 | 276 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 277 | }); 278 | 279 | test("should cancel edits on escape", async ({ page }) => { 280 | const todoItems = page.getByTestId("todo-item"); 281 | await todoItems.nth(1).dblclick(); 282 | await todoItems 283 | .nth(1) 284 | .getByRole("textbox", { name: "Edit" }) 285 | .fill("buy some sausages"); 286 | await todoItems 287 | .nth(1) 288 | .getByRole("textbox", { name: "Edit" }) 289 | .press("Escape"); 290 | await expect(todoItems).toHaveText(TODO_ITEMS); 291 | }); 292 | }); 293 | 294 | test.describe("Counter", () => { 295 | test("should display the current number of todo items", async ({ page }) => { 296 | // create a new todo locator 297 | const newTodo = page.getByPlaceholder("What needs to be done?"); 298 | 299 | // create a todo count locator 300 | const todoCount = page.getByTestId("todo-count"); 301 | 302 | await newTodo.fill(TODO_ITEMS[0]); 303 | await newTodo.press("Enter"); 304 | 305 | await expect(todoCount).toContainText("1"); 306 | 307 | await newTodo.fill(TODO_ITEMS[1]); 308 | await newTodo.press("Enter"); 309 | await expect(todoCount).toContainText("2"); 310 | 311 | await checkNumberOfTodosInLocalStorage(page, 2); 312 | }); 313 | }); 314 | 315 | test.describe("Clear completed button", () => { 316 | test.beforeEach(async ({ page }) => { 317 | await createDefaultTodos(page); 318 | }); 319 | 320 | test("should display the correct text", async ({ page }) => { 321 | await page.locator(".todo-list li .toggle").first().check(); 322 | await expect( 323 | page.getByRole("button", { name: "Clear completed" }), 324 | ).toBeVisible(); 325 | }); 326 | 327 | test("should remove completed items when clicked", async ({ page }) => { 328 | const todoItems = page.getByTestId("todo-item"); 329 | await todoItems.nth(1).getByRole("checkbox").check(); 330 | await page.getByRole("button", { name: "Clear completed" }).click(); 331 | await expect(todoItems).toHaveCount(2); 332 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 333 | }); 334 | 335 | test("should be hidden when there are no items that are completed", async ({ 336 | page, 337 | }) => { 338 | await page.locator(".todo-list li .toggle").first().check(); 339 | await page.getByRole("button", { name: "Clear completed" }).click(); 340 | await expect( 341 | page.getByRole("button", { name: "Clear completed" }), 342 | ).toBeHidden(); 343 | }); 344 | }); 345 | 346 | test.describe("Persistence", () => { 347 | test("should persist its data", async ({ page }) => { 348 | // create a new todo locator 349 | const newTodo = page.getByPlaceholder("What needs to be done?"); 350 | 351 | for (const item of TODO_ITEMS.slice(0, 2)) { 352 | await newTodo.fill(item); 353 | await newTodo.press("Enter"); 354 | } 355 | 356 | const todoItems = page.getByTestId("todo-item"); 357 | const firstTodoCheck = todoItems.nth(0).getByRole("checkbox"); 358 | await firstTodoCheck.check(); 359 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 360 | await expect(firstTodoCheck).toBeChecked(); 361 | await expect(todoItems).toHaveClass(["completed", ""]); 362 | 363 | // Ensure there is 1 completed item. 364 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 365 | 366 | // Now reload. 367 | await page.reload(); 368 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 369 | await expect(firstTodoCheck).toBeChecked(); 370 | await expect(todoItems).toHaveClass(["completed", ""]); 371 | }); 372 | }); 373 | 374 | test.describe("Routing", () => { 375 | test.beforeEach(async ({ page }) => { 376 | await createDefaultTodos(page); 377 | // make sure the app had a chance to save updated todos in storage 378 | // before navigating to a new view, otherwise the items can get lost :( 379 | // in some frameworks like Durandal 380 | await checkTodosInLocalStorage(page, TODO_ITEMS[0]); 381 | }); 382 | 383 | test("should allow me to display active items", async ({ page }) => { 384 | const todoItem = page.getByTestId("todo-item"); 385 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 386 | 387 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 388 | await page.getByRole("link", { name: "Active" }).click(); 389 | await expect(todoItem).toHaveCount(2); 390 | await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 391 | }); 392 | 393 | test("should respect the back button", async ({ page }) => { 394 | const todoItem = page.getByTestId("todo-item"); 395 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 396 | 397 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 398 | 399 | await test.step("Showing all items", async () => { 400 | await page.getByRole("link", { name: "All" }).click(); 401 | await expect(todoItem).toHaveCount(3); 402 | }); 403 | 404 | await test.step("Showing active items", async () => { 405 | await page.getByRole("link", { name: "Active" }).click(); 406 | }); 407 | 408 | await test.step("Showing completed items", async () => { 409 | await page.getByRole("link", { name: "Completed" }).click(); 410 | }); 411 | 412 | await expect(todoItem).toHaveCount(1); 413 | await page.goBack(); 414 | await expect(todoItem).toHaveCount(2); 415 | await page.goBack(); 416 | await expect(todoItem).toHaveCount(3); 417 | }); 418 | 419 | test("should allow me to display completed items", async ({ page }) => { 420 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 421 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 422 | await page.getByRole("link", { name: "Completed" }).click(); 423 | await expect(page.getByTestId("todo-item")).toHaveCount(1); 424 | }); 425 | 426 | test("should allow me to display all items", async ({ page }) => { 427 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 428 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 429 | await page.getByRole("link", { name: "Active" }).click(); 430 | await page.getByRole("link", { name: "Completed" }).click(); 431 | await page.getByRole("link", { name: "All" }).click(); 432 | await expect(page.getByTestId("todo-item")).toHaveCount(3); 433 | }); 434 | 435 | test("should highlight the currently applied filter", async ({ page }) => { 436 | await expect(page.getByRole("link", { name: "All" })).toHaveClass( 437 | "selected", 438 | ); 439 | 440 | // create locators for active and completed links 441 | const activeLink = page.getByRole("link", { name: "Active" }); 442 | const completedLink = page.getByRole("link", { name: "Completed" }); 443 | await activeLink.click(); 444 | 445 | // Page change - active items. 446 | await expect(activeLink).toHaveClass("selected"); 447 | await completedLink.click(); 448 | 449 | // Page change - completed items. 450 | await expect(completedLink).toHaveClass("selected"); 451 | }); 452 | }); 453 | 454 | async function createDefaultTodos(page: Page) { 455 | // create a new todo locator 456 | const newTodo = page.getByPlaceholder("What needs to be done?"); 457 | 458 | for (const item of TODO_ITEMS) { 459 | await newTodo.fill(item); 460 | await newTodo.press("Enter"); 461 | } 462 | } 463 | 464 | async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { 465 | return await page.waitForFunction((e) => { 466 | return JSON.parse(localStorage["react-todos"]).length === e; 467 | }, expected); 468 | } 469 | 470 | async function checkNumberOfCompletedTodosInLocalStorage( 471 | page: Page, 472 | expected: number, 473 | ) { 474 | return await page.waitForFunction((e) => { 475 | return ( 476 | JSON.parse(localStorage["react-todos"]).filter( 477 | (todo: any) => todo.completed, 478 | ).length === e 479 | ); 480 | }, expected); 481 | } 482 | 483 | async function checkTodosInLocalStorage(page: Page, title: string) { 484 | return await page.waitForFunction((t) => { 485 | return JSON.parse(localStorage["react-todos"]) 486 | .map((todo: any) => todo.title) 487 | .includes(t); 488 | }, title); 489 | } 490 | -------------------------------------------------------------------------------- /tests/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "node:fs"; 2 | import path from "node:path"; 3 | import { expect } from "@playwright/test"; 4 | import * as allure from "allure-js-commons"; 5 | import { test } from "../src/fixtures/projectFixtures"; 6 | import { DATA_PATH } from "../src/utilities/constants"; 7 | 8 | test("has title", async ({ page }) => { 9 | await test.step("Navigate to Playwright website", async () => { 10 | await page.goto("https://playwright.dev/"); 11 | }); 12 | await test.step("Check page title", async () => { 13 | await expect(page).toHaveTitle(/Playwright/); 14 | }); 15 | }); 16 | 17 | test( 18 | "get started link", 19 | { 20 | tag: "@devRun", 21 | }, 22 | async ({ page, homePage }, testInfo) => { 23 | await allure.severity(allure.Severity.BLOCKER); 24 | await allure.link("docs", "Related Documentation"); 25 | await allure.issue("issues/AUTH-123", "Related Issue"); 26 | await allure.tms("tms/TMS-456", "Related Test Case"); 27 | await allure.epic("Web interface"); 28 | await allure.owner("John Doe"); 29 | await allure.feature("Essential features"); 30 | await allure.story("Authentication"); 31 | await testInfo.attach("HTML Attachment Example", { 32 | body: "

Example html attachment

", 33 | contentType: "text/html", 34 | }); 35 | await testInfo.attach("Text Attachment Example", { 36 | body: "Some text content", 37 | contentType: "text/plain", 38 | }); 39 | await testInfo.attach("CSV Attachment Example", { 40 | body: "first,second,third\none,two,three", 41 | contentType: "text/csv", 42 | }); 43 | const filePath = path.join(DATA_PATH, "dog.png"); 44 | await testInfo.attach("File Attachment Example", { 45 | body: readFileSync(filePath), 46 | contentType: "image/png", 47 | }); 48 | await testInfo.attach("JSON Attachment Example", { 49 | body: JSON.stringify({ first: 1, second: 2 }, null, 2), 50 | contentType: "application/json", 51 | }); 52 | const xmlContent = 53 | // language=xml 54 | ` 55 | 56 | ... 57 | 58 | `; 59 | await testInfo.attach("XML Attachment Example", { 60 | body: xmlContent, 61 | contentType: "application/xml", 62 | }); 63 | await testInfo.attach("URI List Attachment Example", { 64 | body: [ 65 | "https://github.com/allure-framework", 66 | "https://github.com/allure-examples", 67 | ].join("\n"), 68 | contentType: "text/uri-list", 69 | }); 70 | await test.step("Navigate to the base URL", async () => { 71 | await page.goto("/"); 72 | }); 73 | await test.step('Click the "Get started" link', async () => { 74 | await homePage.clickGetStarted(); 75 | }); 76 | await test.step("Verify heading visibility", async () => { 77 | await expect( 78 | page.getByRole("heading", { name: "Installation" }), 79 | ).toBeVisible(); 80 | }); 81 | }, 82 | ); 83 | -------------------------------------------------------------------------------- /tests/excel.spec.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { expect, test } from "@playwright/test"; 3 | import * as allure from "allure-js-commons"; 4 | import { DATA_PATH } from "../src/utilities/constants"; 5 | import { readExcelFile } from "../src/utilities/fileUtils"; 6 | 7 | const data = await readExcelFile(path.join(DATA_PATH, "data.xls")); 8 | 9 | test.describe("Login flow – data driven", () => { 10 | for (const record of data) { 11 | test(`Login test for ${record.description}`, async ({ page }) => { 12 | await allure.parameter("username", record.user); 13 | await allure.parameter("password", record.password); 14 | await page.goto("https://www.saucedemo.com/"); 15 | if (record.user) await page.getByTestId("username").fill(record.user); 16 | if (record.password) 17 | await page.getByTestId("password").fill(record.password); 18 | await page.getByTestId("login-button").click(); 19 | await expect(page.getByTestId("error")).toHaveText(record.error); 20 | }); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /tests/filechooser.spec.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { test } from "../src/fixtures/projectFixtures"; 3 | 4 | test("file upload using filechooser", async ({ page }) => { 5 | await page.goto( 6 | "https://www.w3schools.com/howto/howto_html_file_upload_button.asp", 7 | ); 8 | 9 | const [fileChooser] = await Promise.all([ 10 | page.waitForEvent("filechooser"), 11 | page 12 | .locator("#myFile") 13 | .click(), // Adjust selector if needed 14 | ]); 15 | await fileChooser.setFiles(path.resolve("resources/dog.png")); 16 | await page.waitForTimeout(5000); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/geolocation.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "../src/fixtures/projectFixtures"; 3 | 4 | test.use({ 5 | geolocation: { longitude: 1, latitude: 1 }, 6 | }); 7 | 8 | test.skip("Geolocation example", async ({ page }) => { 9 | await test.step("Navigate to Playwright website", async () => { 10 | await page.goto("https://playwright.dev/"); 11 | }); 12 | await test.step("Check page title", async () => { 13 | await expect(page).toHaveTitle(/Playwright/); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/launchdarkly.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "../src/fixtures/projectFixtures"; 3 | import { FeatureFlags } from "../src/services/launchDarkly/featureFlags"; 4 | 5 | test.describe("LaunchDarkly Service", () => { 6 | test.skip("should fetch status for a known feature flag", async ({ 7 | launchDarklyService, 8 | }) => { 9 | const flagKey = FeatureFlags.EXAMPLE_FEATURE_A; 10 | const environmentKey = "test"; 11 | const isOn = await launchDarklyService.getFlagStatus( 12 | flagKey, 13 | environmentKey, 14 | ); 15 | expect(isOn).toBe(true); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/mailinator.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "@playwright/test"; 2 | import { test } from "../src/fixtures/projectFixtures"; 3 | 4 | const inbox = "auto1742938795902"; 5 | 6 | test.describe( 7 | "Email integration tests", 8 | { 9 | annotation: [ 10 | { 11 | type: "skip", 12 | description: "Requires mailinator client", 13 | }, 14 | ], 15 | }, 16 | () => { 17 | test("Verify email count in user inbox", async ({ mailinatorService }) => { 18 | const messages = await mailinatorService.getInboxMessages(inbox); 19 | const subjectCounts: Record = {}; 20 | for (const msg of messages) { 21 | subjectCounts[msg.subject] = (subjectCounts[msg.subject] || 0) + 1; 22 | } 23 | expect(subjectCounts).toMatchObject({ 24 | "Verify Your email address": 1, 25 | }); 26 | }); 27 | 28 | test("Verify email content", async ({ mailinatorService }) => { 29 | const message = await mailinatorService.waitForMessageBySubject( 30 | inbox, 31 | "purchase is confirmed", 32 | ); 33 | const body = await mailinatorService.getEmailBody(message.id); 34 | expect(body).toContain("Thank you for your purchase"); 35 | }); 36 | 37 | test("Extract OTP code from email", async ({ mailinatorService }) => { 38 | const otpCode = await mailinatorService.waitForOtp( 39 | inbox, 40 | "Verify Your email address", 41 | ); 42 | expect(/^\d{6}$/.test(otpCode)).toBeTruthy(); 43 | }); 44 | }, 45 | ); 46 | -------------------------------------------------------------------------------- /tests/s3Upload.spec.ts: -------------------------------------------------------------------------------- 1 | import * as path from "node:path"; 2 | import { dirname } from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | import { expect } from "@playwright/test"; 5 | import { test } from "../src/fixtures/projectFixtures"; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = dirname(__filename); 9 | 10 | test.describe("S3 Service Upload", () => { 11 | test.skip("should upload the test.txt file to S3", async ({ s3Service }) => { 12 | const localFileName = "test.txt"; 13 | const bucketName = "test"; 14 | const localFilePath = path.join( 15 | __dirname, 16 | "..", 17 | "resources", 18 | localFileName, 19 | ); 20 | const s3Key = `nir/${localFileName}`; 21 | const uploadResult = await s3Service.uploadFile( 22 | bucketName, 23 | s3Key, 24 | localFilePath, 25 | ); 26 | expect(uploadResult).toBeDefined(); 27 | expect(uploadResult?.$metadata?.httpStatusCode).toBe(200); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/sftpUpload.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from "../src/fixtures/projectFixtures"; 2 | 3 | test.describe("SFTP Service", () => { 4 | test.skip("should upload a specific text file", async ({ sftpService }) => { 5 | const localFileNameToUpload = "test.txt"; 6 | await sftpService.uploadFile(localFileNameToUpload); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 6 | "sourceMap": true, 7 | "outDir": "./build", 8 | "noEmit": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "noImplicitThis": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "skipDefaultLibCheck": true, 17 | "resolveJsonModule": true, 18 | "allowJs": true, 19 | "moduleResolution": "Bundler", 20 | "esModuleInterop": true 21 | }, 22 | "exclude": ["node_modules"], 23 | "include": ["**/*.ts", "**/*.tsx"], 24 | "ts-node": { 25 | "esm": true, 26 | "experimentalSpecifierResolution": "node" 27 | } 28 | } 29 | --------------------------------------------------------------------------------