├── .circleci └── config.yml ├── .eslintrc.json ├── .forceignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── dependabot.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── .yarnrc.yml ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.js ├── config └── project-scratch-def.json ├── force-app ├── main │ └── default │ │ ├── applications │ │ └── UTAM_Recipes.app-meta.xml │ │ ├── flexipages │ │ ├── Hello.flexipage-meta.xml │ │ └── Wire.flexipage-meta.xml │ │ ├── lwc │ │ ├── .eslintrc.json │ │ ├── __utam__ │ │ │ └── homePage.utam.json │ │ ├── errorPanel │ │ │ ├── errorPanel.js │ │ │ ├── errorPanel.js-meta.xml │ │ │ └── templates │ │ │ │ ├── inlineMessage.html │ │ │ │ └── noDataIllustration.html │ │ ├── flexipages │ │ │ └── __utam__ │ │ │ │ └── appFlexipage.utam.json │ │ ├── hello │ │ │ ├── __utam__ │ │ │ │ └── hello.utam.json │ │ │ ├── hello.html │ │ │ ├── hello.js │ │ │ └── hello.js-meta.xml │ │ ├── jsconfig.json │ │ ├── ldsUtils │ │ │ ├── ldsUtils.js │ │ │ └── ldsUtils.js-meta.xml │ │ └── wireGetObjectInfo │ │ │ ├── __utam__ │ │ │ └── wireGetObjectInfo.utam.json │ │ │ ├── wireGetObjectInfo.css │ │ │ ├── wireGetObjectInfo.html │ │ │ ├── wireGetObjectInfo.js │ │ │ └── wireGetObjectInfo.js-meta.xml │ │ ├── permissionsets │ │ └── utam.permissionset-meta.xml │ │ └── tabs │ │ ├── Hello.tab-meta.xml │ │ └── Wire.tab-meta.xml └── test │ ├── .eslintrc.json │ ├── app-navigation.spec.js │ ├── mobile │ ├── playgroundapp │ │ └── navigation.spec.js │ └── salesforceapp │ │ └── login.spec.js │ ├── record-all-items.spec.js │ ├── record-create.spec.js │ ├── record-update.spec.js │ ├── sfdx-scratch-org.spec.js │ ├── utam-portal.spec.js │ └── utilities │ ├── record-type.js │ ├── salesforce-test.js │ └── test-environment.js ├── image.rules.json ├── jsconfig.json ├── package.json ├── scripts ├── check-license-headers.js ├── create-env-file.js ├── generate-login-url.js └── script-utils.js ├── sfdc_metadata └── cred_scan_triage │ └── triage.yaml ├── sfdx-project.json ├── utam-generator ├── README.md ├── compiler.config.json ├── generator.config.json ├── index.js ├── package.json └── src │ ├── cards │ ├── button.html │ ├── icon.html │ ├── image.html │ ├── image.rules.json │ └── text.html │ ├── html │ └── exported.html │ └── lwc │ ├── input.html │ └── recordLayoutItem.html ├── utam-preview ├── README.md ├── index.js ├── package.json ├── src │ └── portal │ │ ├── dummy.utam.json │ │ ├── nullableExample.utam.json │ │ └── utamDevHome.utam.json └── utam-preview.config.js ├── utam.config.js ├── wdio.conf.js ├── wdio.conf.mobilebase.js ├── wdio.conf.mpcc.android.js ├── wdio.conf.mpcc.ios.js ├── wdio.conf.mpccbase.js ├── wdio.conf.sapp.android.js ├── wdio.conf.sapp.ios.js ├── wdio.conf.sappbase.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Shared config 2 | ignore_forks: &ignore_forks 3 | branches: 4 | # Forked pull requests have CIRCLE_BRANCH set to pull/XXX 5 | ignore: /pull\/[0-9]+/ 6 | 7 | only_forks: &only_forks 8 | branches: 9 | # Forked pull requests have CIRCLE_BRANCH set to pull/XXX 10 | only: /pull\/[0-9]+/ 11 | 12 | deploy_filters: &deploy_filters 13 | filters: 14 | branches: 15 | ignore: /.*/ 16 | tags: 17 | # Trigger on tags that begin with `v` 18 | only: /^v.*/ 19 | 20 | # Jobs definition 21 | version: 2.1 22 | 23 | # Custom commands definition 24 | commands: 25 | save_workspace: 26 | description: Save current workspace 27 | steps: 28 | - persist_to_workspace: 29 | root: . 30 | paths: . 31 | 32 | load_workspace: 33 | description: Load workspace 34 | steps: 35 | - attach_workspace: 36 | at: ~/utam-js-recipes 37 | 38 | # Orb for yarn 39 | orbs: 40 | node: circleci/node@5.2.0 41 | 42 | # Jobs definition 43 | jobs: 44 | build: 45 | executor: 46 | name: node/default 47 | tag: '18.18' 48 | resource_class: medium 49 | steps: 50 | - checkout 51 | - run: 52 | name: Yarn Set Version Stable 53 | command: | 54 | yarn set version 4.5.1 55 | - node/install-packages: 56 | check-cache: always 57 | pkg-manager: yarn-berry 58 | cache-path: '~/.yarn/cache' 59 | - run: 60 | name: Build 61 | command: yarn build 62 | - run: 63 | name: Check missing file headers 64 | command: node ./scripts/check-license-headers.js 65 | - run: 66 | name: Check formatting 67 | command: yarn prettier --check 'force-app/**/*.{js,ts,json,md}' 68 | 69 | # Workflows definition 70 | workflows: 71 | version: 2 72 | build: 73 | jobs: 74 | - build 75 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es2021": true 6 | }, 7 | "parser": "@babel/eslint-parser", 8 | "rules": {} 9 | } 10 | -------------------------------------------------------------------------------- /.forceignore: -------------------------------------------------------------------------------- 1 | **/jsconfig.json 2 | 3 | **/.eslintrc.json 4 | 5 | **/__utam__/** 6 | 7 | test -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Bug]" 5 | labels: bug, needs triaging 6 | --- 7 | 8 | ### Actual behavior 9 | 10 | 11 | 12 | 13 | 14 | - [ ] Unable to compile a Page Object 15 | 16 | - [ ] Invalid generated code 17 | 18 | - [ ] Runtime error 19 | 20 | ### What is your expected behavior? 21 | 22 | 23 | ### Steps to reproduce 24 | 25 | 26 | ### Environment 27 | 28 | - Failing test [e.g force-app/test/record-create.spec.js] 29 | 30 | - Node.js: [e.g 14.18.2] vX.Y.Z 31 | - OS: [e.g. iOS] 32 | - Browser: [e.g. chrome, safari] 33 | - Browser Version: [e.g. 22] 34 | 35 | ### Additional context 36 | 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feat Request]" 5 | labels: feature request, needs triaging 6 | --- 7 | 8 | ### Is your feature request related to a problem? Please describe 9 | 10 | 11 | ### Describe the solution you'd like 12 | 13 | 14 | ### Describe alternatives you've considered 15 | 16 | 17 | ### Additional context 18 | 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "thursday" 8 | time: "09:00" 9 | timezone: "America/Los_Angeles" 10 | ignore: 11 | # Base TS image (used for tsserver) must be update manually 12 | # when updating the default node.js runtime 13 | - dependency-name: "@tsconfig/*" 14 | open-pull-requests-limit: 10 15 | groups: 16 | babel: 17 | patterns: 18 | - "@babel/*" 19 | utam: 20 | patterns: 21 | - "@utam/*" 22 | - "utam" 23 | - "wdio-utam-service" 24 | webdriverio: 25 | patterns: 26 | - "@wdio/*" 27 | 28 | - package-ecosystem: "github-actions" 29 | directory: "/" 30 | schedule: 31 | interval: "weekly" 32 | day: "thursday" 33 | time: "09:00" 34 | timezone: "America/Los_Angeles" 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS artifacts 2 | .DS_STORE 3 | 4 | # Tools artifacts 5 | node_modules 6 | coverage 7 | .eslintcache 8 | *.log 9 | *.pem 10 | tsconfig.tsbuildinfo 11 | tsconfig.build.tsbuildinfo 12 | dump.rdb 13 | logfile 14 | .sfdx 15 | .sf 16 | 17 | # Project artifacts 18 | __benchmarks_results__/ 19 | testing_dev/ 20 | 21 | # Builds 22 | packages/*/build/ 23 | packages/*/dist/ 24 | 25 | # Generated Page Objects 26 | **/pageObjects/ 27 | 28 | # Generated Utils 29 | **/utils/ 30 | 31 | # Generated .env 32 | .env 33 | **.env 34 | 35 | # npmrc 36 | .npmrc 37 | 38 | # Generated JSON files 39 | utam-generator/__utam__/**.json 40 | utam-generator/__generated__/**.json 41 | # HTML sources 42 | utam-generator/src/** 43 | 44 | # Generated sarif reports 45 | **/utam-lint.sarif 46 | 47 | # yarn - If you're not using Zero-Installs: https://yarnpkg.com/features/caching#zero-installs 48 | .pnp.* 49 | .yarn/* 50 | !.yarn/patches 51 | !.yarn/plugins 52 | !.yarn/releases 53 | !.yarn/sdks 54 | !.yarn/versions 55 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/**/*.md 2 | build/ 3 | dist/ 4 | node_modules/ 5 | **/pageObjects/ 6 | **/utils/ 7 | *.md 8 | .sfdx 9 | .sf -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "overrides": [ 7 | { 8 | "files": "*.html", 9 | "options": { "parser": "lwc" } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug 'utam-preview' page objects build", 6 | "request": "launch", 7 | "runtimeArgs": ["compile"], 8 | "runtimeExecutable": "yarn", 9 | "cwd": "${workspaceFolder}/utam-preview", 10 | "skipFiles": ["/**"], 11 | "type": "pwa-node" 12 | }, 13 | { 14 | "name": "Debug 'force-app' page objects build", 15 | "request": "launch", 16 | "runtimeArgs": ["build"], 17 | "runtimeExecutable": "yarn", 18 | "cwd": "${workspaceFolder}", 19 | "skipFiles": ["/**"], 20 | "type": "pwa-node" 21 | }, 22 | { 23 | "name": "Debug wdio test", 24 | "env": { 25 | "CHROME_HEADLESS": "false", 26 | "DEBUG": "true" 27 | }, 28 | "type": "node", 29 | "request": "launch", 30 | "cwd": "${workspaceRoot}", 31 | "envFile": "${workspaceRoot}/.env", 32 | "program": "./node_modules/.bin/wdio", 33 | "args": ["--spec", "${file}"], 34 | "console": "integratedTerminal", 35 | "skipFiles": ["/**/*.js", "*/node_modules/**/*.js"], 36 | "autoAttachChildProcesses": true 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.utam.json": "jsonc" 4 | }, 5 | "json.schemas": [ 6 | { 7 | "fileMatch": ["*.utam.json"], 8 | "url": "https://json.schemastore.org/utam-page-object.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | npmRegistryServer: "https://registry.yarnpkg.com" 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing. 2 | #ECCN:Open Source 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Salesforce Open Source Community Code of Conduct 2 | 3 | ## About the Code of Conduct 4 | 5 | Equality is a core value at Salesforce. We believe a diverse and inclusive 6 | community fosters innovation and creativity, and are committed to building a 7 | culture where everyone feels included. 8 | 9 | Salesforce open-source projects are committed to providing a friendly, safe, and 10 | welcoming environment for all, regardless of gender identity and expression, 11 | sexual orientation, disability, physical appearance, body size, ethnicity, nationality, 12 | race, age, religion, level of experience, education, socioeconomic status, or 13 | other similar personal characteristics. 14 | 15 | The goal of this code of conduct is to specify a baseline standard of behavior so 16 | that people with different social values and communication styles can work 17 | together effectively, productively, and respectfully in our open source community. 18 | It also establishes a mechanism for reporting issues and resolving conflicts. 19 | 20 | All questions and reports of abusive, harassing, or otherwise unacceptable behavior 21 | in a Salesforce open-source project may be reported by contacting the Salesforce 22 | Open Source Conduct Committee at ossconduct@salesforce.com. 23 | 24 | ## Our Pledge 25 | 26 | In the interest of fostering an open and welcoming environment, we as 27 | contributors and maintainers pledge to making participation in our project and 28 | our community a harassment-free experience for everyone, regardless of gender 29 | identity and expression, sexual orientation, disability, physical appearance, 30 | body size, ethnicity, nationality, race, age, religion, level of experience, education, 31 | socioeconomic status, or other similar personal characteristics. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to creating a positive environment 36 | include: 37 | 38 | - Using welcoming and inclusive language 39 | - Being respectful of differing viewpoints and experiences 40 | - Gracefully accepting constructive criticism 41 | - Focusing on what is best for the community 42 | - Showing empathy toward other community members 43 | 44 | Examples of unacceptable behavior by participants include: 45 | 46 | - The use of sexualized language or imagery and unwelcome sexual attention or 47 | advances 48 | - Personal attacks, insulting/derogatory comments, or trolling 49 | - Public or private harassment 50 | - Publishing, or threatening to publish, others' private information—such as 51 | a physical or electronic address—without explicit permission 52 | - Other conduct which could reasonably be considered inappropriate in a 53 | professional setting 54 | - Advocating for or encouraging any of the above behaviors 55 | 56 | ## Our Responsibilities 57 | 58 | Project maintainers are responsible for clarifying the standards of acceptable 59 | behavior and are expected to take appropriate and fair corrective action in 60 | response to any instances of unacceptable behavior. 61 | 62 | Project maintainers have the right and responsibility to remove, edit, or 63 | reject comments, commits, code, wiki edits, issues, and other contributions 64 | that are not aligned with this Code of Conduct, or to ban temporarily or 65 | permanently any contributor for other behaviors that they deem inappropriate, 66 | threatening, offensive, or harmful. 67 | 68 | ## Scope 69 | 70 | This Code of Conduct applies both within project spaces and in public spaces 71 | when an individual is representing the project or its community. Examples of 72 | representing a project or community include using an official project email 73 | address, posting via an official social media account, or acting as an appointed 74 | representative at an online or offline event. Representation of a project may be 75 | further defined and clarified by project maintainers. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported by contacting the Salesforce Open Source Conduct Committee 81 | at ossconduct@salesforce.com. All complaints will be reviewed and investigated 82 | and will result in a response that is deemed necessary and appropriate to the 83 | circumstances. The committee is obligated to maintain confidentiality with 84 | regard to the reporter of an incident. Further details of specific enforcement 85 | policies may be posted separately. 86 | 87 | Project maintainers who do not follow or enforce the Code of Conduct in good 88 | faith may face temporary or permanent repercussions as determined by other 89 | members of the project's leadership and the Salesforce Open Source Conduct 90 | Committee. 91 | 92 | ## Attribution 93 | 94 | This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home], 95 | version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html. 96 | It includes adaptions and additions from [Go Community Code of Conduct][golang-coc], 97 | [CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc]. 98 | 99 | This Code of Conduct is licensed under the [Creative Commons Attribution 3.0 License][cc-by-3-us]. 100 | 101 | [contributor-covenant-home]: https://www.contributor-covenant.org "https://www.contributor-covenant.org/" 102 | [golang-coc]: https://golang.org/conduct 103 | [cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md 104 | [microsoft-coc]: https://opensource.microsoft.com/codeofconduct/ 105 | [cc-by-3-us]: https://creativecommons.org/licenses/by/3.0/us/ 106 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to UTAM JavaScript recipes 2 | 3 | We encourage the developer community to contribute to UTAM JavaScript recipes. 4 | 5 | > **Note: It might take months before we can review a pull request. Please be patient!** 6 | 7 | - [Code of Conduct](#code-of-conduct) 8 | - [Requirements](#requirements) 9 | - [Installation](#installation) 10 | - [Git Workflow](#git-workflow) 11 | 12 | ## [Code of Conduct](./CODE_OF_CONDUCT.md) 13 | 14 | The UTAM JavaScript recipes project has a [Code of Conduct](./CODE_OF_CONDUCT.md) to which all contributors must adhere. 15 | 16 | ## Requirements 17 | 18 | - [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli) 19 | - [Node](https://nodejs.org/) >= 16 20 | - [Yarn](https://yarnpkg.com/) >= 4 21 | 22 | This project uses [Volta](https://volta.sh/) to ensure that all the contributors share the same version of `Node` and `Yarn` for development. If you are considering making frequent contributions to this project, we recommend installing Volta. 23 | 24 | If you install Volta, run this command to install node and yarn: 25 | 26 | ```bash 27 | $ volta install node yarn 28 | ``` 29 | 30 | If you're having issues with Yarn and Volta, see [Yarn's documentation on Corepack](https://yarnpkg.com/corepack#volta). 31 | 32 | ## Installation 33 | 34 | [Set up SSH access to GitHub][setup-github-ssh] if you haven't done so already. 35 | 36 | ### 1) Setup your Salesforce DX environment 37 | 38 | If you don't have an SFDX environment set up, you need to set up one before contributing to the project. 39 | To set up your Salesforce DX environment, follow the [Prerequisites][readme-prerequisites] and [Org Setup][readme-org-setup] sections from the [README](./README.md). 40 | 41 | ### 2) Fork the repository 42 | 43 | We recommend that you [fork][fork-a-repo] the [salesforce/utam-js-recipes](https://github.com/salesforce/utam-js-recipes) repo. 44 | 45 | After you fork the repo, [clone][clone-a-repo] your fork in your local workspace: 46 | 47 | ```bash 48 | $ git clone git@github.com/utam-js-recipes.git 49 | $ cd utam-js-recipes 50 | ``` 51 | 52 | ### 3) Install dependencies 53 | 54 | _We use [yarn](https://yarnpkg.com/) because it is significantly faster than npm for our use case. See this command [cheatsheet](https://yarnpkg.com/lang/en/docs/migrating-from-npm/)._ 55 | 56 | ```bash 57 | $ yarn install 58 | ``` 59 | 60 | ### 4) Build the project 61 | 62 | Execute `yarn build` to build the project and generate page objects. 63 | 64 | If you change a page object or test later, run `yarn build` to update the project. 65 | 66 | ### 5) Run Tests 67 | 68 | Once the project has been built, execute tests as indicated in the [running tests][readme-running-tests] section of the README. 69 | 70 | ## Git Workflow 71 | 72 | The process of submitting a pull request is straightforward and generally follows the same pattern each time: 73 | 74 | 1. [Fork the UTAM JS Recipes repo](#fork-the-utam-js-recipes-repo) 75 | 2. [Create a feature branch](#create-a-feature-branch) 76 | 3. [Make your changes](#make-your-changes) 77 | 4. [Rebase](#rebase) 78 | 5. [Create a pull request](#create-a-pull-request) 79 | 6. [Update the pull request](#update-the-pull-request) 80 | 81 | ### Fork the UTAM JS Recipes repo 82 | 83 | [Fork][fork-a-repo] the [salesforce/utam-js-recipes](https://github.com/salesforce/utam-js-recipes) repo. Clone your fork in your local workspace and [configure][configuring-a-remote-for-a-fork] your remote repository settings. 84 | 85 | ```bash 86 | $ git clone git@github.com:/utam-js-recipes.git 87 | $ cd utam-js-recipes 88 | $ git remote add upstream git@github.com:salesforce/utam-js-recipes.git 89 | ``` 90 | 91 | ### Create a feature branch 92 | 93 | ```bash 94 | $ git checkout master 95 | $ git pull origin master 96 | $ git checkout -b 97 | ``` 98 | 99 | ### Make your changes 100 | 101 | Modify the files, lint, format and commit your code using the following commands: 102 | 103 | ```bash 104 | $ git add 105 | $ git commit 106 | $ git push origin / 107 | ``` 108 | 109 | The above commands will commit the files into your feature branch. You can keep 110 | pushing new changes into the same branch until you are ready to create a pull 111 | request. 112 | 113 | ### Rebase 114 | 115 | Sometimes your feature branch will get stale with respect to the master branch, 116 | and it will require a rebase. The following steps can help: 117 | 118 | ```bash 119 | $ git checkout master 120 | $ git pull origin master 121 | $ git checkout 122 | $ git rebase upstream/master 123 | ``` 124 | 125 | > Note: If no conflicts arise, these commands will ensure that your changes are applied on top of the master branch. Any conflicts will have to be manually resolved. 126 | 127 | ### Create a pull request 128 | 129 | If you've never created a pull request before, follow [these instructions][creating-a-pull-request]. 130 | 131 | ### Update the pull request 132 | 133 | ```bash 134 | $ git fetch origin 135 | $ git rebase origin/${base_branch} 136 | 137 | # If there were no merge conflicts in the rebase 138 | $ git push origin ${feature_branch} 139 | 140 | # If there was a merge conflict that was resolved 141 | $ git push origin ${feature_branch} --force 142 | ``` 143 | 144 | > Note: If more changes are needed as part of the pull request, just keep committing and pushing your feature branch as described above and the pull request will automatically update. 145 | 146 | [clone-a-repo]: https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository 147 | [fork-a-repo]: https://help.github.com/en/articles/fork-a-repo 148 | [configuring-a-remote-for-a-fork]: https://help.github.com/en/articles/configuring-a-remote-for-a-fork 149 | [setup-github-ssh]: https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/ 150 | [readme-prerequisites]: ./README.md#prerequisites 151 | [readme-org-setup]: ./README.md#org-setup 152 | [readme-running-tests]: ./README.md#running-tests 153 | [creating-a-pull-request]: https://help.github.com/articles/creating-a-pull-request/ 154 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Salesforce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UTAM JavaScript Recipes 2 | 3 | This repository contains examples of how to test the Salesforce UI using the [UTAM][utam-doc] framework. 4 | 5 | > [!NOTE] 6 | > This repository uses UTAM JavaScript. If you want to use UTAM with Java, visit the [UTAM Java recipes repository][utam-java-recipes]. 7 | 8 | > [!NOTE] 9 | > These recipes are designed to work with a generic Salesforce org. If your org has customizations, you might need to modify page objects or tests locally to avoid errors. 10 | 11 | > [!IMPORTANT] 12 | > __This repository's page objects and UI tests are compatible with the Salesforce Spring'24 release.__ 13 | 14 | ## Project structure 15 | 16 | This repository contains two npm packages. Both packages demonstrate how to set up page object authoring and compilation. 17 | 18 | 19 | ### 1) utam-js-recipes package (project root) 20 | 21 | This package contains: 22 | 23 | - Custom components that can be deployed to a scratch org 24 | - Page objects associated with those custom components 25 | - UI tests 26 | - Scripts that ease the initial setup 27 | 28 | Here's an outline of the directory structure and few of the important configuration files. 29 | 30 | ```txt 31 | ├── force-app // contains JSON page objects and tests 32 | ├── pageObjects (created after build) 33 | ├── package.json 34 | ├── utam.config.js 35 | └── wdio.conf.js 36 | ``` 37 | 38 | The repo has a [hello](./force-app/main/default/lwc/hello) Lightning web component. The JSON page object is in a `__utam__` folder beside the component source. 39 | 40 | ```txt 41 | ├── lwc 42 | ├── hello 43 | ├── hello.html 44 | ├── hello.js 45 | ├── hello.js-meta.xml 46 | └── __utam__ 47 | └── hello.utam.json 48 | ``` 49 | 50 | ### 2) utam-preview package 51 | 52 | This package contains the page objects used in the UI tests that interact with the Salesforce UI. 53 | 54 | Both packages demonstrate how to setup page objects authoring and compilation. 55 | 56 | ## Requirements 57 | 58 | - [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli) 59 | - [Node](https://nodejs.org/) >= 16 60 | - [Yarn](https://yarnpkg.com/) >= 4 61 | 62 | ## Initial setup 63 | 64 | ### 1) Clone the repository 65 | 66 | Clone the `utam-js-recipes` repository: 67 | 68 | ```sh 69 | $ git clone git@github.com:salesforce/utam-js-recipes.git 70 | $ cd utam-js-recipes 71 | ``` 72 | 73 | ### 2) Install dependencies 74 | 75 | ```sh 76 | $ yarn install 77 | ``` 78 | 79 | ### 3) Build the project 80 | 81 | Execute `yarn build` to generate page objects: 82 | 83 | ```sh 84 | $ yarn build 85 | ``` 86 | 87 | There are two types of tests in this project: 88 | 89 | 1. The first test (`force-app/test/crud.spec.js`) loads a provided URL and logs in through the standard login page before beginning the test. 90 | 2. The other test (`force-app/test/sfdx-scratch-org.spec.js`) runs against the custom app and components in this project by loading the app in a scratch org. 91 | 92 | Both tests demonstrate how UTAM can be used to author and compile page objects, and how to integrate the UTAM runtime with WebdriverIO. 93 | 94 | ## Dependency for Salesforce page objects 95 | 96 | The `package.json` file contains a dependency for the `salesforce-pageobjects` package, which contains page objects from Salesforce. The package is available on [npm](https://www.npmjs.com/package/salesforce-pageobjects). 97 | 98 | ## Set up Salesforce Web UI tests 99 | 100 | ### 1) Create a .env file 101 | 102 | We use a `.env` file to contain the URL and authentication credentials for test environments that we use. 103 | 104 | > [!NOTE] 105 | > Do not commit your `.env` files. Those files contain sensitive credentials. The repository is set up so that we don't track those files by default. 106 | 107 | Create a `.env` file by executing: 108 | 109 | ```sh 110 | $ yarn create:env 111 | ``` 112 | 113 | This script creates a `.env` file located at the project root. 114 | 115 | ### 2) Configure your test environment variables 116 | 117 | Open the `.env` file created during the previous step and configure your test environment credentials. 118 | 119 | Here's a `.env` file that references a `SANDBOX` test environment. Each variable for the test environment starts with the test environment name followed by an underscore. 120 | 121 | ```shell-script 122 | # Required variables 123 | SANDBOX_URL=https://sandbox.salesforce.com/ 124 | SANDBOX_USERNAME=your.username@salesforce.com 125 | SANDBOX_PASSWORD=strongPassword 126 | 127 | # Optional variables 128 | # sometimes after login URL changes 129 | SANDBOX_REDIRECT_URL=https://lightningapp.lightning.test1234.salesforce.com/ 130 | 131 | # Used in force-app/test/record-update.spec.js 132 | SANDBOX_ACCOUNT_ID=accountId 133 | SANDBOX_CONTACT_ID=contactId 134 | ``` 135 | 136 | Replace SANDBOX with your test environment name. 137 | 138 | A test references a test environment name in a call to the `TestEnvironment` constructor. For example: 139 | 140 | ```java 141 | const TEST_ENVIRONMENT_PREFIX = 'sandbox'; 142 | const testEnvironment = new TestEnvironment(TEST_ENVIRONMENT_PREFIX); 143 | ``` 144 | 145 | The environment name must be all uppercase in the `.env` file but the name is case insensitive in the JavaScript code. The environment name of `sandbox` in the test code matches the uppercase `SANDBOX` name in the `.env` file. A camel case environment name of `sandboxOrg` in the test code would match an uppercase `SANDBOX_ORG` name in the `.env` file. 146 | 147 | > [!NOTE] 148 | > Add as many test environments as needed in your `.env` file. Just duplicate the variables and adjust the prefix and the values. 149 | 150 | Alternatively, if you don't want to configure a `.env` file, you can prefix the test command with environment variables: 151 | 152 | ```sh 153 | $ SANDBOX_URL=my-sandbox.com SANDBOX_USERNAME=user@salesforce.com SANDBOX_PASSWORD=password yarn test --spec force-app/test/record-*.spec.js 154 | ``` 155 | 156 | ### 3) Update the Web UI tests 157 | 158 | Open the Web UI test files located in: 159 | 160 | - `force-app/test/record-create.spec.js` 161 | - `force-app/test/record-update.spec.js` 162 | 163 | For each test file, update the value of the `TEST_ENVIRONMENT_PREFIX` global variable located after the import statements: 164 | 165 | ```js 166 | // Assuming your test environment is sandbox (must match the prefix used in the .env file) 167 | const TEST_ENVIRONMENT_PREFIX = 'sandbox'; 168 | ``` 169 | 170 | For the `force-app/test/record-update.spec.js` file, update your `.env` file with the account and contact IDs of your test environment. That's how it looks like for an environment named `sandbox`: 171 | 172 | ``` 173 | SANDBOX_ACCOUNT_ID=XXXXXXXXXXXXXXXXXX 174 | SANDBOX_CONTACT_ID=XXXXXXXXXXXXXXXXXX 175 | ```` 176 | 177 | ## Setup SFDX scratch org test 178 | 179 | ### Prerequisites 180 | 181 | Follow the steps in the [Quick Start: Lightning Web Components](https://trailhead.salesforce.com/content/learn/projects/quick-start-lightning-web-components/) Trailhead project. The steps include: 182 | 183 | - Enable Dev Hub in your Trailhead Playground 184 | - Install Salesforce CLI 185 | - (Optional) Install Visual Studio Code 186 | - (Optional) Install the Visual Studio Code Salesforce extensions, including the Lightning Web Components extension 187 | 188 | ### Org Setup 189 | 190 | 1. If you haven't already done so, authorize your hub org and provide it with an alias (**myhuborg** in the command below). Use the login credentials generated from your Trailhead Playground in the Prerequisites section above or your own Developer Edition org if you prefer: 191 | 192 | ```sh 193 | $ sfdx auth:web:login -d -a myhuborg 194 | ``` 195 | 196 | 2. Create a scratch org and provide it with an alias (**utam-js-recipes** in the command below): 197 | 198 | ```sh 199 | $ sfdx force:org:create -s -f config/project-scratch-def.json -a utam-js-recipes 200 | ``` 201 | 202 | > [!NOTE] 203 | > If this step throws an error `ERROR running force:org:create: You do not have access to the [ScratchOrgInfo] object`, you must [**enable Dev Hub**][enable-dev-hub]. 204 | > To enable **Dev Hub**: 205 | > 1. Log in to the org you authenticated against during step 1 in a web browser. 206 | > 2. Click on the Setup icon in the upper right corner. 207 | > 3. Click Setup. 208 | > 4. Search for `dev hub` using the quick find search box on the left pane. 209 | > 5. Click on the `Dev Hub` item under `Development`. 210 | > 6. Click on the `Enable Dev Hub` toggle. 211 | > 7. Create a scratch org using the `sfdx force:org:create` command mentioned previously 212 | 213 | 3. Push the app to your scratch org: 214 | 215 | ```sh 216 | $ sfdx force:source:push 217 | ``` 218 | 219 | 4. Assign the **utam** permission set to the default user: 220 | 221 | ```sh 222 | $ sfdx force:user:permset:assign -n utam 223 | ``` 224 | 225 | > [!NOTE] 226 | > If this step throws an error `Permission set not found in target org`, run `sfdx plugins:install user` and repeat from step 3 227 | 228 | 5. Open the scratch org: 229 | 230 | ```sh 231 | $ sfdx force:org:open 232 | ``` 233 | 234 | If you need to recreate a scratch org: 235 | 236 | - find created org `sfdx force:org:list --all` 237 | - delete previously created org with `sfdx force:org:delete`. It will prompt you to delete the org from the list, or specify an org alias or email `sfdx force:org:delete -u utam-js-recipes` 238 | - recreate scratch orgs (repeat steps starting from step 3) 239 | 240 | ## Running UI tests 241 | 242 | Execute all tests at once by running: 243 | 244 | ```sh 245 | $ yarn test 246 | ``` 247 | 248 | This command runs all UI tests in the repository, namely all tests in `force-app/test/` folder. 249 | 250 | ### Run the Web UI test 251 | 252 | These tests require login credentials to an existing org. Make sure that your test environment is set up as described in [Set up Salesforce Web UI tests](#set-up-salesforce-web-ui-tests). 253 | 254 | Run the Web UI tests against the environment you configured: 255 | 256 | ```sh 257 | $ yarn test --spec force-app/test/record-create.spec.js 258 | $ yarn test --spec force-app/test/record-update.spec.js 259 | ``` 260 | 261 | To run all tests related to records, run: 262 | 263 | ```sh 264 | $ yarn test --spec force-app/test/record-*.spec.js 265 | ``` 266 | > [!NOTE] 267 | > CRUD tests will modify real records in the org so only sandbox or development-specific orgs should be used. 268 | 269 | ### Run the local app in a scratch org test 270 | 271 | These tests run under the assumption that the initial URL loaded contains an access token so no manual login is required. 272 | To generate such a URL, follow the Org Setup steps above and then run: 273 | 274 | ```sh 275 | $ yarn generate:login 276 | ``` 277 | 278 | This command runs the `sfdx` command below and adds the generated URL to the `.env` file in the root of this project. 279 | 280 | ```sh 281 | $ sfdx force:org:open -p /lightning -r 282 | ``` 283 | 284 | Finally, run tests: 285 | 286 | ```sh 287 | $ yarn test --spec force-app/test/sfdx-scratch-org.spec.js 288 | ``` 289 | 290 | ### Run the test against the UTAM doc site 291 | 292 | The repository contains a [test against utam.dev](https://github.com/salesforce/utam-js-recipes/blob/main/force-app/test/utam-portal.spec.js). 293 | The test doesn't require any special setup. The instructions to run it are inside the test. 294 | 295 | [enable-dev-hub]: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_setup_enable_devhub.htm 296 | [utam-doc]: https://utam.dev 297 | [utam-java-recipes]: https://www.github.com/salesforce/utam-java-recipes 298 | 299 | ## Run Salesforce Mobile test 300 | 301 | - There are two sample tests under `force-app/test/mobile`: `navigation.spec.js` is against Community playground application and `login.spec.js` is against Salelsforce application. 302 | - Follow the instructions at [Get Started for Mobile](https://utam.dev/guide/get_started_utam#get-started-for-mobile) to set up your local simulator/emulator. 303 | - Make sure that [Appium](https://github.com/appium/appium#readme) and [node.js](https://nodejs.org/en/) are installed on your machine. 304 | - Update the wdio configuration file: 305 | For an iOS test, update `wdio.conf.xx.ios.js` ((wdio.conf.sapp.ios.js is for test against Salesforce App, and wdio.conf.mpcc.ios.js is for test against Community Playground App) file to configure the test device name(appium:deviceName), iOS version(appium:platformVersion), and the full path for the test application(appium:app): 306 | 307 | ```js 308 | 'appium:deviceName': 'iPhone 12', 309 | 'appium:app': '', 310 | 'appium:platformVersion': '15.2', 311 | ``` 312 | 313 | For an Android test, update the `wdio.conf.xx.android.js` (wdio.conf.sapp.android.js is for test against Salesforce App, and wdio.conf.mpcc.android.js is for test against Community Playground App) file to configure the test device name(appium:deviceName) and the full path for the test application(appium:app): 314 | 315 | ```js 316 | 'appium:deviceName': 'emulator-5554', 317 | 'appium:app': '', 318 | ``` 319 | 320 | - Download the [Salesforce application Build](https://developer.salesforce.com/tools/mobile-debugging) and [Community playground application build](https://help.salesforce.com/s/articleView?id=sf.s1_branded_apps_playground_preview_exp_site.htm&type=5) for the Salesforce iOS and Android mobile application debug builds. 321 | - Commands to execute a test: 322 | For iOS: yarn test wdio.conf.xx.ios.js (wdio.conf.sapp.ios.js is for test against Salesforce App, and wdio.conf.mpcc.ios.js is for test against Community Playground App) 323 | For Android: yarn test wdio.conf.xx.android.js (wdio.conf.sapp.android.js is for test against Salesforce App, and wdio.conf.mpcc.android.js is for test against Community Playground App) 324 | - For a test on Android, make sure to start an emulator before the test run. Otherwise, you will get an error like this: "Error: Failed to create session. 325 | An unknown server-side error occurred while processing the command. Original error: Could not find a connected Android device in 20190ms.". 326 | - Install the appropriate version of chromedriver based on the instructions on this [site](https://github.com/appium/appium/tree/master#drivers). 327 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: '18', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Lightning UI Testing Scratch Org", 3 | "edition": "Developer", 4 | "settings": { 5 | "lightningExperienceSettings": { 6 | "enableS1DesktopEnabled": true 7 | }, 8 | "userEngagementSettings": { 9 | "enableShowSalesforceUserAssist": false 10 | }, 11 | "mobileSettings": { 12 | "enableS1EncryptedStoragePref2": false 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /force-app/main/default/applications/UTAM_Recipes.app-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Easy-to-digest code examples for UTAM 4 | Large 5 | false 6 | false 7 | 8 | Standard 9 | Hello 10 | Wire 11 | Lightning 12 | -------------------------------------------------------------------------------- /force-app/main/default/flexipages/Hello.flexipage-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | region1 5 | Region 6 | 7 | 8 | 9 | 10 | hello 11 | 12 | 13 | region2 14 | Region 15 | 16 | Hello 17 | 20 | AppPage 21 | -------------------------------------------------------------------------------- /force-app/main/default/flexipages/Wire.flexipage-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | region1 5 | Region 6 | 7 | 8 | 9 | 10 | wireGetObjectInfo 11 | 12 | 13 | region3 14 | Region 15 | 16 | Wire 17 | 20 | AppPage 21 | 22 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@salesforce/eslint-config-lwc/recommended"], 3 | "overrides": [ 4 | { 5 | "files": ["*.test.js"], 6 | "rules": { 7 | "@lwc/lwc/no-unexpected-wire-adapter-usages": "off" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/__utam__/homePage.utam.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "selector": { 4 | "css": "body" 5 | }, 6 | "elements": [ 7 | { 8 | "name": "navigationBar", 9 | "type": "salesforce-pageobjects/global/pageObjects/appNav", 10 | "public": true, 11 | "selector": { 12 | "css": "one-appnav" 13 | } 14 | }, 15 | { 16 | "name": "activeFlexiPage", 17 | "selector": { 18 | "css": ".oneContent.active app_flexipage-lwc-app-flexipage" 19 | }, 20 | "type": "utam-sfdx/pageObjects/appFlexipage", 21 | "public": true 22 | } 23 | ], 24 | "methods": [ 25 | { 26 | "name": "getComponent", 27 | "compose": [ 28 | { 29 | "element": "activeFlexiPage" 30 | }, 31 | { 32 | "chain": true, 33 | "apply": "waitForLoad", 34 | "returnType": "utam-sfdx/pageObjects/appFlexipage" 35 | }, 36 | { 37 | "chain": true, 38 | "element": "flexipageComponent2", 39 | "returnType": "salesforce-pageobjects/flexipage/pageObjects/component2" 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/errorPanel/errorPanel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | import { reduceErrors } from 'c/ldsUtils'; 9 | import { api, LightningElement } from 'lwc'; 10 | import inlineMessage from './templates/inlineMessage.html'; 11 | import noDataIllustration from './templates/noDataIllustration.html'; 12 | 13 | export default class ErrorPanel extends LightningElement { 14 | /** Single or array of LDS errors */ 15 | @api errors; 16 | /** Generic / user-friendly message */ 17 | @api friendlyMessage = 'Error retrieving data'; 18 | /** Type of error message **/ 19 | @api type; 20 | 21 | viewDetails = false; 22 | 23 | get errorMessages() { 24 | return reduceErrors(this.errors); 25 | } 26 | 27 | handleShowDetailsClick() { 28 | this.viewDetails = !this.viewDetails; 29 | } 30 | 31 | render() { 32 | if (this.type === 'inlineMessage') return inlineMessage; 33 | return noDataIllustration; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/errorPanel/errorPanel.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 49.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/errorPanel/templates/inlineMessage.html: -------------------------------------------------------------------------------- 1 | 7 | 24 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/errorPanel/templates/noDataIllustration.html: -------------------------------------------------------------------------------- 1 | 7 | 241 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/flexipages/__utam__/appFlexipage.utam.json: -------------------------------------------------------------------------------- 1 | { 2 | "methods": [ 3 | { 4 | "name": "waitForLoad", 5 | "compose": [ 6 | { 7 | "apply": "waitFor", 8 | "args": [ 9 | { 10 | "type": "function", 11 | "predicate": [ 12 | { 13 | "element": "module", 14 | "apply": "containsElement", 15 | "args": [ 16 | { 17 | "type": "locator", 18 | "value": { 19 | "css": ".forcegenerated-flexipage-template" 20 | } 21 | }, 22 | { 23 | "value": true 24 | } 25 | ] 26 | } 27 | ] 28 | } 29 | ] 30 | }, 31 | { 32 | "apply": "returnSelf" 33 | } 34 | ] 35 | } 36 | ], 37 | "shadow": { 38 | "elements": [ 39 | { 40 | "name": "internal", 41 | "selector": { 42 | "css": "app_flexipage-lwc-app-flexipage-internal" 43 | }, 44 | "shadow": { 45 | "elements": [ 46 | { 47 | "name": "adgRollup", 48 | "selector": { 49 | "css": ".adg-rollup-wrapped" 50 | }, 51 | "shadow": { 52 | "elements": [ 53 | { 54 | "name": "module", 55 | "selector": { 56 | "css": ".forcegenerated-flexipage-module" 57 | }, 58 | "shadow": { 59 | "elements": [ 60 | { 61 | "name": "template", 62 | "selector": { 63 | "css": ".forcegenerated-flexipage-template" 64 | }, 65 | "elements": [ 66 | { 67 | "name": "flexipageComponent2", 68 | "type": "salesforce-pageobjects/flexipage/pageObjects/component2", 69 | "selector": { 70 | "css": "flexipage-component2" 71 | }, 72 | "public": true 73 | } 74 | ] 75 | } 76 | ] 77 | } 78 | } 79 | ] 80 | } 81 | } 82 | ] 83 | } 84 | } 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/hello/__utam__/hello.utam.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadow": { 3 | "elements": [ 4 | { 5 | "name": "lightningCard", 6 | "selector": { 7 | "css": "lightning-card" 8 | }, 9 | "type": "salesforce-pageobjects/lightning/pageObjects/card" 10 | } 11 | ] 12 | }, 13 | "methods": [ 14 | { 15 | "name": "getText", 16 | "compose": [ 17 | { 18 | "element": "lightningCard", 19 | "apply": "getBodyText", 20 | "returnType": "string" 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/hello/hello.html: -------------------------------------------------------------------------------- 1 | 7 | 12 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/hello/hello.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | import { LightningElement } from 'lwc'; 9 | 10 | export default class Hello extends LightningElement { 11 | greeting = 'World'; 12 | } 13 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/hello/hello.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50.0 4 | true 5 | 6 | lightning__AppPage 7 | lightning__RecordPage 8 | lightning__HomePage 9 | 10 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "baseUrl": ".", 5 | "paths": { 6 | "c/*": ["*"] 7 | } 8 | }, 9 | "include": ["**/*", "../../../../.sfdx/typings/lwc/**/*.d.ts"], 10 | "paths": { 11 | "c/*": ["*"] 12 | }, 13 | "typeAcquisition": { 14 | "include": ["jest"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/ldsUtils/ldsUtils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | /** 9 | * Reduces one or more LDS errors into a string[] of error messages. 10 | * @param {FetchResponse|FetchResponse[]} errors 11 | * @return {String[]} Error messages 12 | */ 13 | export function reduceErrors(errors) { 14 | if (!Array.isArray(errors)) { 15 | errors = [errors]; 16 | } 17 | 18 | return ( 19 | errors 20 | // Remove null/undefined items 21 | .filter((error) => !!error) 22 | // Extract an error message 23 | .map((error) => { 24 | // UI API read errors 25 | if (Array.isArray(error.body)) { 26 | return error.body.map((e) => e.message); 27 | } 28 | // UI API DML, Apex and network errors 29 | else if (error.body && typeof error.body.message === 'string') { 30 | return error.body.message; 31 | } 32 | // JS errors 33 | else if (typeof error.message === 'string') { 34 | return error.message; 35 | } 36 | // Unknown error shape so try HTTP status text 37 | return error.statusText; 38 | }) 39 | // Flatten 40 | .reduce((prev, curr) => prev.concat(curr), []) 41 | // Remove empty strings 42 | .filter((message) => !!message) 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/ldsUtils/ldsUtils.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 49.0 4 | false 5 | 6 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/wireGetObjectInfo/__utam__/wireGetObjectInfo.utam.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadow": { 3 | "elements": [ 4 | { 5 | "name": "lightningInput", 6 | "public": true, 7 | "type": "salesforce-pageobjects/lightning/pageObjects/input", 8 | "selector": { 9 | "css": "lightning-input" 10 | } 11 | }, 12 | { 13 | "name": "lightningButton", 14 | "public": true, 15 | "type": "salesforce-pageobjects/lightning/pageObjects/button", 16 | "selector": { 17 | "css": "lightning-button" 18 | } 19 | }, 20 | { 21 | "name": "content", 22 | "selector": { 23 | "css": "div.slds-card__body pre" 24 | } 25 | } 26 | ] 27 | }, 28 | "methods": [ 29 | { 30 | "name": "searchAndWaitForResponse", 31 | "compose": [ 32 | { 33 | "element": "lightningInput", 34 | "apply": "setText", 35 | "args": [ 36 | { 37 | "type": "string", 38 | "name": "textToSearch" 39 | } 40 | ] 41 | }, 42 | { 43 | "element": "lightningButton", 44 | "apply": "click" 45 | }, 46 | { 47 | "apply": "waitFor", 48 | "args": [ 49 | { 50 | "type": "function", 51 | "predicate": [ 52 | { 53 | "element": "root", 54 | "apply": "containsElement", 55 | "args": [ 56 | { 57 | "type": "locator", 58 | "value": { 59 | "css": "div.slds-card__body pre" 60 | } 61 | }, 62 | { 63 | "value": true 64 | } 65 | ] 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | ] 72 | }, 73 | { 74 | "name": "getContent", 75 | "compose": [ 76 | { 77 | "element": "content", 78 | "apply": "getText" 79 | } 80 | ] 81 | } 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/wireGetObjectInfo/wireGetObjectInfo.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | .scroller { 9 | height: 220px; 10 | overflow: auto; 11 | padding: var(--lwc-varSpacingXSmall, 8px); 12 | border: solid var(--lwc-borderWidthThin, 1px) #eee; 13 | } 14 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/wireGetObjectInfo/wireGetObjectInfo.html: -------------------------------------------------------------------------------- 1 | 7 | 25 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/wireGetObjectInfo/wireGetObjectInfo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | import { getObjectInfo } from 'lightning/uiObjectInfoApi'; 9 | import { LightningElement, wire } from 'lwc'; 10 | 11 | export default class WireGetObjectInfo extends LightningElement { 12 | objectApiName; 13 | 14 | @wire(getObjectInfo, { objectApiName: '$objectApiName' }) 15 | objectInfo; 16 | 17 | handleBtnClick() { 18 | this.objectApiName = this.template.querySelector('lightning-input').value; 19 | } 20 | 21 | get objectInfoStr() { 22 | return this.objectInfo ? JSON.stringify(this.objectInfo.data, null, 2) : ''; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/wireGetObjectInfo/wireGetObjectInfo.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 49.0 4 | true 5 | 6 | lightning__AppPage 7 | lightning__RecordPage 8 | lightning__HomePage 9 | 10 | 11 | -------------------------------------------------------------------------------- /force-app/main/default/permissionsets/utam.permissionset-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | UTAM_Recipes 5 | true 6 | 7 | false 8 | 9 | 10 | true 11 | false 12 | false 13 | true 14 | false 15 | Account 16 | false 17 | 18 | 19 | true 20 | true 21 | true 22 | true 23 | true 24 | Contact 25 | true 26 | 27 | 28 | Hello 29 | Visible 30 | 31 | 32 | Wire 33 | Visible 34 | 35 | -------------------------------------------------------------------------------- /force-app/main/default/tabs/Hello.tab-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created by Lightning App Builder 4 | Hello 5 | 6 | 7 | Custom14: Hands 8 | -------------------------------------------------------------------------------- /force-app/main/default/tabs/Wire.tab-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created by Lightning App Builder 4 | Wire 5 | 6 | 7 | Custom67: Gears 8 | 9 | -------------------------------------------------------------------------------- /force-app/test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:jasmine/recommended", "plugin:wdio/recommended"], 3 | "plugins": ["jasmine", "wdio"], 4 | "env": { 5 | "node": true, 6 | "es2021": true, 7 | "jasmine": true 8 | }, 9 | "globals": { 10 | "utam": "readonly" 11 | }, 12 | "parserOptions": { 13 | "sourceType": "module", 14 | "ecmaVersion": "latest" 15 | }, 16 | "rules": { 17 | "jasmine/new-line-before-expect": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /force-app/test/app-navigation.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // to run: 9 | // yarn test --spec force-app/test/app-navigation.spec.js 10 | 11 | import ApplicationHome from 'salesforce-pageobjects/navex/pageObjects/desktopLayoutContainer'; 12 | import { login } from './utilities/salesforce-test'; 13 | import { TestEnvironment } from './utilities/test-environment'; 14 | 15 | // TODO: replace with prefix of the environment from .env file 16 | const TEST_ENVIRONMENT_PREFIX = 'na44'; 17 | 18 | describe('Sales app navigation tests', () => { 19 | const testEnvironment = new TestEnvironment(TEST_ENVIRONMENT_PREFIX); 20 | 21 | it('Application bar navigation', async () => { 22 | await login(testEnvironment, testEnvironment.redirectUrl); 23 | console.log('Load Home Page'); 24 | const homePage = await utam.load(ApplicationHome); 25 | const appNav = await homePage.getAppNav(); 26 | const appNavBar = await appNav.getAppNavBar(); 27 | const navItem = await appNavBar.getNavItem('Accounts'); 28 | console.log('Click "Accounts" and wait for account home to load'); 29 | await navItem.clickAndWaitForUrl('Account'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /force-app/test/mobile/playgroundapp/navigation.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | import AddCommunity from 'salesforce-pageobjects/playgroundapp/pageObjects/authentication/addCommunity'; 9 | import Blogs from 'salesforce-pageobjects/playgroundapp/pageObjects/navigation/blogs'; 10 | import Eula from 'salesforce-pageobjects/playgroundapp/pageObjects/authentication/eula'; 11 | import Home from 'salesforce-pageobjects/playgroundapp/pageObjects/navigation/home'; 12 | import NavTabBar from 'salesforce-pageobjects/playgroundapp/pageObjects/navigation/navTabBar'; 13 | 14 | /** 15 | * @param {string} communityURL 16 | */ 17 | async function updateCommunity(communityURL) { 18 | const prefix = 'https://'; 19 | const addCommunity = await utam.load(AddCommunity); 20 | await addCommunity.addCommunityUrl(browser.isAndroid ? communityURL : prefix.concat(communityURL)); 21 | } 22 | 23 | const SAMPLE_COMMUNITY = 'mobpub-recipes.my.site.com/capricornjuices/s/'; 24 | 25 | describe('Test Community Playground App', () => { 26 | it('testNavigation', async () => { 27 | // Accept Eula 28 | const eula = await utam.load(Eula); 29 | await eula.accept(); 30 | // Setup community 31 | await updateCommunity(SAMPLE_COMMUNITY); 32 | 33 | // Navigate to Blog 34 | let tabBar = await utam.load(NavTabBar); 35 | await tabBar.clickTabBarItem('Blog'); 36 | utam.setBridgeAppTitle('All Blogs Final'); 37 | const blogs = await utam.load(Blogs); 38 | const blogList = await blogs.getBlogPostCellList(); 39 | expect((await blogList.getBlogPostCells()).length).toBeGreaterThan(0); 40 | 41 | // Navigate to Home 42 | tabBar = await utam.load(NavTabBar); 43 | await tabBar.clickTabBarItem('Home'); 44 | utam.setBridgeAppTitle('Home'); 45 | const home = await utam.load(Home); 46 | await home.clickShopNow(); 47 | await home.clickReadMore(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /force-app/test/mobile/salesforceapp/login.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | import ChooseConn from 'salesforce-pageobjects/salesforceapp/pageObjects/authentication/chooseConn'; 9 | import Eula from 'salesforce-pageobjects/salesforceapp/pageObjects/authentication/eula'; 10 | import Login from 'salesforce-pageobjects/helpers/pageObjects/login'; 11 | import LoginNavBar from 'salesforce-pageobjects/salesforceapp/pageObjects/authentication/loginNavBar'; 12 | import LoginNavBarOptions from 'salesforce-pageobjects/salesforceapp/pageObjects/authentication/loginNavBarOptions'; 13 | 14 | describe('Test Salesforce App', () => { 15 | it('testLogin', async () => { 16 | const eula = await utam.load(Eula); 17 | await eula.accept(); 18 | 19 | const loginNavBar = await utam.load(LoginNavBar); 20 | await loginNavBar.chooseConnOption(); 21 | 22 | if (browser.isAndroid) { 23 | const options = await utam.load(LoginNavBarOptions); 24 | await options.changeServer(); 25 | } 26 | 27 | const choosConn = await utam.load(ChooseConn); 28 | await choosConn.switchConnection('Sandbox'); 29 | 30 | if (browser.isAndroid) { 31 | browser.back(); 32 | } 33 | 34 | utam.setBridgeAppTitle('Login | Salesforce'); 35 | await utam.load(Login); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /force-app/test/record-all-items.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // to run: 9 | // yarn test --spec force-app/test/record-all-items.spec.js 10 | 11 | import { RecordType } from './utilities/record-type'; 12 | import { login, openRecordModal } from './utilities/salesforce-test'; 13 | import { TestEnvironment } from './utilities/test-environment'; 14 | 15 | // TODO: replace with prefix of the environment from .env file 16 | const TEST_ENVIRONMENT_PREFIX = 'na44'; 17 | 18 | describe('Test LwcRecordLayout methods', () => { 19 | const testEnvironment = new TestEnvironment(TEST_ENVIRONMENT_PREFIX); 20 | const baseUrl = testEnvironment.redirectUrl; 21 | 22 | it('Test LwcRecordLayout.getSections() waits for sections', async () => { 23 | await login(testEnvironment, baseUrl); 24 | const recordFormModal = await openRecordModal(baseUrl, RecordType.Opportunity); 25 | const recordForm = await recordFormModal.getRecordForm(); 26 | const recordLayout = await recordForm.getRecordLayout(); 27 | 28 | // test sections count 29 | const sections = await recordLayout.getSections(); 30 | const sectionsCount = sections.length; 31 | console.log('Number of sections: ' + sectionsCount); 32 | expect(sectionsCount).toBeGreaterThan(0); 33 | 34 | // test items count 35 | const items = await recordLayout.getAllItems(); 36 | const itemsCount = items.length; 37 | console.log('Number of items: ' + itemsCount); 38 | expect(itemsCount).toBeGreaterThan(0); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /force-app/test/record-create.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // to run: 9 | // yarn test --spec force-app/test/record-create.spec.js 10 | 11 | import RecordHomeFlexipage2 from 'salesforce-pageobjects/global/pageObjects/recordHomeFlexipage2'; 12 | import { RecordType } from './utilities/record-type'; 13 | import { login, openRecordModal } from './utilities/salesforce-test'; 14 | import { TestEnvironment } from './utilities/test-environment'; 15 | import RecordActionWrapper from 'salesforce-pageobjects/global/pageObjects/recordActionWrapper'; 16 | 17 | // TODO: replace with prefix of the environment from .env file 18 | const TEST_ENVIRONMENT_PREFIX = 'na44'; 19 | 20 | describe('Record creation tests', () => { 21 | const testEnvironment = new TestEnvironment(TEST_ENVIRONMENT_PREFIX); 22 | const baseUrl = testEnvironment.redirectUrl; 23 | 24 | beforeAll(async () => { 25 | await login(testEnvironment, 'home'); 26 | }); 27 | 28 | it('Create a new Account Record', async () => { 29 | let recordFormModal = await openRecordModal(baseUrl, RecordType.Account); 30 | // TODO - depending on org setup, modal might not present, then comment next lines 31 | /* 32 | console.log('Load Change Record Type Modal'); 33 | recordFormModal = await utam.load(RecordActionWrapper); 34 | console.log("Change Record Type Modal: click button 'Next'"); 35 | const changeRecordTypeFooter = await recordFormModal.waitForChangeRecordFooter(); 36 | await changeRecordTypeFooter.clickButton('Next'); 37 | */ 38 | 39 | console.log('Load Record Form Modal'); 40 | recordFormModal = await utam.load(RecordActionWrapper); 41 | const recordForm = await recordFormModal.getRecordForm(); 42 | const recordLayout = await recordForm.getRecordLayout(); 43 | 44 | console.log('Access record form item by index'); 45 | const item = await recordLayout.getItem(1, 2, 1); 46 | 47 | console.log('Enter account name'); 48 | const accountName = 'Utam'; 49 | const input = await item.getTextInput(); 50 | await input.setText(accountName); 51 | 52 | console.log('Save new record'); 53 | await recordForm.clickFooterButton('Save'); 54 | await recordFormModal.waitForAbsence(); 55 | 56 | console.log('Load Accounts Record Home page'); 57 | await utam.load(RecordHomeFlexipage2); 58 | }); 59 | 60 | it('Create a new Opportunity Record', async () => { 61 | console.log('Load Record Form Modal'); 62 | const recordFormModal = await openRecordModal(baseUrl, RecordType.Opportunity); 63 | const recordForm = await recordFormModal.getRecordForm(); 64 | const recordLayout = await recordForm.getRecordLayout(); 65 | 66 | console.log("Enter 'Close date' as 01/01/2020"); 67 | const closeDateItem = await recordLayout.getItem(1, 2, 2); 68 | const datePicker = await closeDateItem.getDatepicker(); 69 | await datePicker.setDateText('01/01/2020'); 70 | 71 | console.log("Pick first option in a 'Stage' combobox"); 72 | const stageItem = await recordLayout.getItem(1, 4, 2); 73 | const stageCombobox = await (await stageItem.getStageNamePicklist()).getBaseCombobox(); 74 | await utam.waitFor( 75 | async () => { 76 | await stageCombobox.expandForDisabledInput(); 77 | await stageCombobox.pickItem(2); 78 | return true; 79 | }, 80 | { message: 'cant pick opportunity name' }, 81 | ); 82 | 83 | console.log('Find and pick first account, link it to the opportunity'); 84 | const accountLookupItem = await recordLayout.getItem(1, 4, 1); 85 | const accountLookup = await (await accountLookupItem.getLookup()).getBaseCombobox(); 86 | await accountLookup.expand(); 87 | await accountLookup.pickItem(1); 88 | 89 | console.log('Enter opportunity name'); 90 | const nameItem = await recordLayout.getItem(1, 3, 1); 91 | const nameInput = await nameItem.getTextInput(); 92 | await nameInput.setText('Opportunity name'); 93 | 94 | console.log('Save new record'); 95 | await recordForm.clickFooterButton('Save'); 96 | await recordFormModal.waitForAbsence(); 97 | 98 | console.log('Load Record Home page'); 99 | await utam.load(RecordHomeFlexipage2); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /force-app/test/record-update.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // to run: 9 | // yarn test --spec force-app/test/record-update.spec.js 10 | 11 | import RecordHomeFlexipage2 from 'salesforce-pageobjects/global/pageObjects/recordHomeFlexipage2'; 12 | import RecordActionWrapper from 'salesforce-pageobjects/global/pageObjects/recordActionWrapper'; 13 | import Tab2 from 'salesforce-pageobjects/flexipage/pageObjects/tab2'; 14 | import { RecordType } from './utilities/record-type'; 15 | import { login } from './utilities/salesforce-test'; 16 | import { TestEnvironment } from './utilities/test-environment'; 17 | 18 | // TODO: replace with prefix of the environment from .env file 19 | const TEST_ENVIRONMENT_PREFIX = 'na44'; 20 | 21 | describe('Record update test', () => { 22 | const testEnvironment = new TestEnvironment(TEST_ENVIRONMENT_PREFIX); 23 | 24 | /** 25 | * Utility function that returns a given record URL 26 | * @param {RecordType} recordType type of record used in the UI test 27 | * @param {string} recordId id of the record for which we are getting the URL 28 | */ 29 | async function gotoRecordHomeByUrl(recordType, recordId) { 30 | const recordHomeUrl = recordType.getRecordHomeUrl(testEnvironment.redirectUrl, recordId); 31 | console.log(`Navigate to the Record Home by URL: ${recordHomeUrl}`); 32 | await browser.navigateTo(recordHomeUrl); 33 | } 34 | 35 | /** 36 | * Case-insensitive equality comparaison between two strings 37 | * @param {string} str1 first string to compare 38 | * @param {string} str2 other string to compare 39 | * @returns {boolean} true if the string are equals, false otherwise 40 | */ 41 | const equalsIgnoreCase = (str1, str2) => str1.toLowerCase() === str2.toLowerCase(); 42 | 43 | beforeAll(async () => { 44 | await login(testEnvironment, 'home'); 45 | }); 46 | 47 | it('Update an existing Account Record', async () => { 48 | await gotoRecordHomeByUrl(RecordType.Account, testEnvironment.accountId); 49 | 50 | console.log('Load Accounts Record Home page'); 51 | const recordHome = await utam.load(RecordHomeFlexipage2); 52 | 53 | console.log('Access Record Highlights panel'); 54 | const highlightsPanel = await recordHome.getHighlights(); 55 | 56 | console.log("Wait for button 'Edit' and click on it"); 57 | const actionsRibbon = await highlightsPanel.getActions(); 58 | const editButton = await actionsRibbon.getActionRendererWithTitle('Edit'); 59 | await editButton.clickButton(); 60 | 61 | console.log('Load Record Form Modal'); 62 | const recordFormModal = await utam.load(RecordActionWrapper); 63 | const recordForm = await recordFormModal.getRecordForm(); 64 | const recordLayout = await recordForm.getRecordLayout(); 65 | 66 | console.log('Access record form item by index'); 67 | const item = await recordLayout.getItem(1, 2, 1); 68 | 69 | console.log('Enter updated account name'); 70 | const accountName = 'Utam'; 71 | const input = await item.getTextInput(); 72 | await input.setText(accountName); 73 | 74 | console.log('Save updated record'); 75 | await recordForm.clickFooterButton('Save'); 76 | await recordFormModal.waitForAbsence(); 77 | }); 78 | 79 | it('Inline edit existing Contact Record', async () => { 80 | async function gotToRecordDetailsTab() { 81 | console.log('Load Record Home page'); 82 | const recordHome = await utam.load(RecordHomeFlexipage2); 83 | const tabset = await recordHome.getTabset(); 84 | 85 | const detailsTabLabel = 'Details'; 86 | console.log('Select "Details" tab'); 87 | const tabBar = await tabset.getTabBar(); 88 | const activeTabName = await tabBar.getActiveTabText(); 89 | if (!equalsIgnoreCase(activeTabName, detailsTabLabel)) { 90 | await tabBar.clickTab(detailsTabLabel); 91 | } 92 | const detailPanel = await (await tabset.getActiveTabContent(Tab2)).getDetailPanel(); 93 | return await detailPanel.getBaseRecordForm(); 94 | } 95 | 96 | await gotoRecordHomeByUrl(RecordType.Contact, testEnvironment.contactId); 97 | const baseRecordForm = await gotToRecordDetailsTab(); 98 | let recordLayout = await baseRecordForm.getRecordLayout(); 99 | 100 | console.log('Access Name field on Details panel'); 101 | let nameItem = await recordLayout.getItem(1, 2, 1); 102 | console.log('Remember value of the name field'); 103 | let formattedName = await nameItem.getFormattedName(); 104 | const nameString = await formattedName.getInnerText(); 105 | 106 | console.log('Click inline edit (pencil) next to the Name field'); 107 | const inlineEditButton = await nameItem.getInlineEditButton(); 108 | await inlineEditButton.click(); 109 | 110 | console.log('Click Save at the bottom of Details panel'); 111 | const footer = await baseRecordForm.getFooter(); 112 | const actionsRibbon = await footer.getActionsRibbon(); 113 | const actionRenderer = await actionsRibbon.getActionRendererWithTitle('Save'); 114 | const headlessAction = await actionRenderer.getHeadlessAction(); 115 | const button = await headlessAction.getLightningButton(); 116 | await button.click(); 117 | 118 | console.log('Wait for page to reload'); 119 | await button.waitForAbsence(); 120 | 121 | const reloaded = await gotToRecordDetailsTab(); 122 | recordLayout = await reloaded.getRecordLayout(); 123 | nameItem = await recordLayout.getItem(1, 2, 1); 124 | 125 | console.log('Wait for field to be updated'); 126 | await nameItem.waitForOutputField(); 127 | formattedName = await nameItem.getFormattedName(); 128 | expect(await formattedName.getInnerText()).toBe(nameString); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /force-app/test/sfdx-scratch-org.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // to run: 9 | // yarn test --spec force-app/test/sfdx-scratch-org.spec.js 10 | 11 | import Hello from '../../pageObjects/hello'; 12 | import HomePage from '../../pageObjects/homePage'; 13 | import WireGetObjectInfo from '../../pageObjects/wireGetObjectInfo'; 14 | import { TestEnvironment } from './utilities/test-environment'; 15 | 16 | describe('Scratch Org Tests', () => { 17 | const testEnvironment = new TestEnvironment('scratchOrg'); 18 | let /** @type {HomePage} */ appHomePage; 19 | 20 | beforeEach(async () => { 21 | console.log('Navigate to login URL for a scratch org'); 22 | await browser.navigateTo(testEnvironment.sfdxLoginUrl); 23 | 24 | console.log('Wait for Home Page URL'); 25 | const domDocument = utam.getCurrentDocument(); 26 | await domDocument.waitFor(async () => (await domDocument.getUrl()).includes('Hello')); 27 | 28 | console.log('Wait for Application Home Page to load'); 29 | appHomePage = await utam.load(HomePage); 30 | }); 31 | 32 | it('hello: displays greeting', async () => { 33 | console.log('Wait for Flexipage with Hello component to load'); 34 | const component2 = await appHomePage.getComponent(); 35 | const hello = await component2.getContent(Hello); 36 | 37 | console.log('Get and assert text inside the component"'); 38 | const text = await hello.getText(); 39 | expect(text).toContain('Hello, World!'); 40 | }); 41 | 42 | it('wire: get object info for Contact', async () => { 43 | console.log("Click 'Wire' in app navigation menu and wait for URL navigation"); 44 | const appNav = await appHomePage.getNavigationBar(); 45 | const appNavBar = await appNav.getAppNavBar(); 46 | const item = await appNavBar.getNavItem('Wire'); 47 | await item.clickAndWaitForUrl('lightning/n/Wire'); 48 | 49 | console.log('Wait for Flexipage with Wire component to load'); 50 | const component2 = await appHomePage.getComponent(); 51 | const wireInfo = await component2.getContent(WireGetObjectInfo); 52 | 53 | console.log("Enter search criteria and click 'Search', wait for response"); 54 | await wireInfo.searchAndWaitForResponse('Contact'); 55 | 56 | console.log('Get and assert response content'); 57 | const text = await wireInfo.getContent(); 58 | expect(text).toContain(`"apiName": "Contact"`); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /force-app/test/utam-portal.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // to run: 9 | // yarn test --spec force-app/test/utam-portal.spec.js 10 | 11 | import UtamDevHome from 'utam-preview/pageObjects/utamDevHome'; 12 | import Dummy from 'utam-preview/pageObjects/dummy'; 13 | import NullableExample from 'utam-preview/pageObjects/nullableExample'; 14 | 15 | describe('Test utam.dev portal', () => { 16 | beforeEach(async () => { 17 | console.log('Navigate to portal'); 18 | await browser.navigateTo('https://utam.dev'); 19 | }); 20 | 21 | it('Menu links navigation', async () => { 22 | console.log('Load Home Page'); 23 | const homePage = await utam.load(UtamDevHome); 24 | const menuItems = await homePage.getMenuItems(); 25 | expect(menuItems.length).toBe(6); 26 | 27 | console.log('Click Grammar menu item and check navigation'); 28 | await (await homePage.getGrammarMenuItem()).click(); 29 | expect(await browser.getUrl()).toBe('https://utam.dev/grammar/spec'); 30 | }); 31 | 32 | it('Validate root element presence', async () => { 33 | console.log('Assert that a root page is not loaded'); 34 | const domDocument = utam.getCurrentDocument(); 35 | // random root page object that we know is not present 36 | expect(await domDocument.containsObject(Dummy)).toBeFalse(); 37 | 38 | console.log('Assert that a root element with a given locator is not present'); 39 | expect(await domDocument.containsElement(utam.By.css('idonotexist'))).toBeFalse(); 40 | }); 41 | 42 | it('Checking absence of the elements on the page', async () => { 43 | console.log('Load Home Page'); 44 | const homePage = await utam.load(NullableExample); 45 | 46 | console.log('Non existing nullable basic element is returned as null'); 47 | expect(await homePage.getNullableBasicElement()).toBeNull(); 48 | 49 | console.log('Non existing nullable basic elements list is returned as null'); 50 | expect(await homePage.getNullableBasicElementList()).toBeNull(); 51 | 52 | console.log('Non existing nullable custom element is returned as null'); 53 | expect(await homePage.getNullableCustomElement()).toBeNull(); 54 | 55 | console.log('Non existing nullable custom elements list is returned as null'); 56 | expect(await homePage.getNullableCustomElementList()).toBeNull(); 57 | 58 | console.log('Nullable element scoped inside non existing nullable basic element is returned as null'); 59 | expect(await homePage.getScopedInsideNullable()).toBeNull(); 60 | }); 61 | 62 | it('Test with stale elements', async () => { 63 | console.log('Confirm that everything is present and visible'); 64 | let homePage = await utam.load(UtamDevHome); 65 | await homePage.waitForVisible(); 66 | expect(await homePage.isPresent()).toBeTrue(); 67 | expect(await homePage.isVisible()).toBeTrue(); 68 | 69 | console.log('Get home page content, save as a variable'); 70 | let pageContent = await homePage.getContent(); 71 | expect(await pageContent.isPresent()).toBeTrue(); 72 | 73 | console.log('Reload web page by navigating to its URL again'); 74 | await browser.navigateTo('https://utam.dev'); 75 | 76 | console.log('Because we reloaded content, all elements became stale'); 77 | await homePage.waitForAbsence(); 78 | expect(await homePage.isPresent()).toBeFalse(); 79 | await pageContent.waitForAbsence(); 80 | expect(await pageContent.isPresent()).toBeFalse(); 81 | 82 | console.log('Reload the root to invoke Driver.findElement'); 83 | homePage = await utam.load(UtamDevHome); 84 | expect(await homePage.isPresent()).toBeTrue(); 85 | 86 | console.log('Call getter to invoke Element.findElement and assign new variable'); 87 | pageContent = await homePage.getContent(); 88 | expect(await pageContent.isPresent()).toBeTrue(); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /force-app/test/utilities/record-type.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | /** 9 | * Class that represents a RecordType 'enum' used for record UI tests 10 | */ 11 | export class RecordType { 12 | static Account = new RecordType('Account'); 13 | static Contact = new RecordType('Contact'); 14 | static Opportunity = new RecordType('Opportunity'); 15 | 16 | /** 17 | * Create a record type 18 | * @param {'Account'|'Contact'|'Opportunity'} name record name as specified in the static properties 19 | */ 20 | constructor(name) { 21 | this.name = name; 22 | } 23 | 24 | /** 25 | * Utility function that returns the object homepage URL 26 | * @param {string} baseUrl Test environment base URL read from environment file 27 | * @returns {string} the object home URL of a specific record type 28 | */ 29 | getObjectHomeUrl(baseUrl) { 30 | return `${baseUrl}lightning/o/${this.name}/list?filterName=Recent`; 31 | } 32 | 33 | /** 34 | * Utility function that returns the record homepage URL 35 | * @param {string} redirectUrl Test environment redirection URL read from environment file 36 | * @param {string} recordId Id of the record 37 | * @returns {string} the record home URL of a specific record type 38 | */ 39 | getRecordHomeUrl(redirectUrl, recordId) { 40 | if (!recordId) { 41 | throw new Error('Record ID is not set'); 42 | } 43 | return `${redirectUrl}lightning/r/${this.name}/${recordId}/view`; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /force-app/test/utilities/salesforce-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | import Login from 'salesforce-pageobjects/helpers/pageObjects/login'; 9 | import ConsoleObjectHome from 'salesforce-pageobjects/global/pageObjects/consoleObjectHome'; 10 | import RecordActionWrapper from 'salesforce-pageobjects/global/pageObjects/recordActionWrapper'; 11 | 12 | /** 13 | * Helper function used in crud tests to login in STMFA environment 14 | * @param {import("./test-environment").TestEnvironment} testEnvironment 15 | * @param {string} landingPagePartialUrl 16 | */ 17 | export async function login(testEnvironment, landingPagePartialUrl) { 18 | const { baseUrl, username, password } = testEnvironment; 19 | 20 | console.log(`Navigate to login URL: ${baseUrl}`); 21 | await browser.url(baseUrl); 22 | const loginPage = await utam.load(Login); 23 | await loginPage.login(username, password); 24 | const document = utam.getCurrentDocument(); 25 | await document.waitFor(async () => { 26 | const docUrl = await document.getUrl(); 27 | return docUrl.includes(landingPagePartialUrl); 28 | }); 29 | } 30 | 31 | /** 32 | * Utility function that open a given record type modal 33 | * @param {string} baseUrl test environment 34 | * @param {import("./record-type").RecordType} recordType type of record used in the UI test 35 | * @returns {Promise} instance of the record modal Page Object 36 | */ 37 | export async function openRecordModal(baseUrl, recordType) { 38 | console.log(`Navigate to an Object Home for ${recordType.name}`); 39 | await browser.navigateTo(recordType.getObjectHomeUrl(baseUrl)); 40 | console.log(`Load ${recordType.name} Object Home page`); 41 | const objectHome = await utam.load(ConsoleObjectHome); 42 | const listView = await objectHome.getListView(); 43 | const listViewHeader = await listView.getHeader(); 44 | 45 | console.log("List view header: click button 'New'"); 46 | const actionLink = await listViewHeader.waitForAction('New'); 47 | await actionLink.click(); 48 | 49 | console.log('Load Record Form Modal'); 50 | const recordFormModal = await utam.load(RecordActionWrapper); 51 | const isRecordFormModalPresent = await recordFormModal.isPresent(); 52 | expect(isRecordFormModalPresent).toBe(true); 53 | return recordFormModal; 54 | } 55 | -------------------------------------------------------------------------------- /force-app/test/utilities/test-environment.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | /** 9 | * Utility class that reads test environment information from environment variables (process.env object). 10 | * You must create a .env file located at the project root that initializes the test environment variables. 11 | * For instance, assuming that the test environment prefix is "sandbox", the .env file should be set up like 12 | * this: 13 | * 14 | * SANDBOX_URL=https://sandbox.salesforce.com/ 15 | * SANDBOX_USERNAME=my.user@salesforce.com 16 | * SANDBOX_PASSWORD=secretPassword 17 | * SANDBOX_REDIRECTION_URL=https://another.url.salesforce.com 18 | * 19 | * Only URL, USERNAME and PASSWORD are mandatory in the .env file. Make sure to separate the prefix and the property 20 | * name with underscores (i.e SANDBOX_URL). 21 | * 22 | * You can also use this class to write UI tests against a scratch org. To test against a scratch org, 23 | * make sure you .env file contains a property named SCRATCH_ORG_LOGIN_URL that holds the login URL value. 24 | * For instance, assuming that the test environment prefix is "myScratchOrg", the .env file should be set up like this: 25 | * 26 | * MY_SCRATCH_ORG_LOGIN_URL=https://orgdomain.salesforce.com/... 27 | * 28 | * When creating an instance of this class, pass the environment name prefix as a parameter to the constructor 29 | * (i.e new TestEnvironment("sandbox")) 30 | */ 31 | 32 | const SESSION_TIMEOUT = 2 * 60 * 60 * 1000; // 2 hours by default 33 | const SCRATCH_ORG_PREFIX = 'scratchOrg'; 34 | 35 | export class TestEnvironment { 36 | #envPrefix; 37 | #baseUrl; 38 | #redirectUrl; 39 | #username; 40 | #password; 41 | #sfdxLoginUrl; 42 | #accountId; 43 | #contactId; 44 | 45 | /** 46 | * Create a new test environment utility class 47 | * @param {string} envNamePrefix Test environment prefix 48 | */ 49 | constructor(envNamePrefix) { 50 | this.#envPrefix = this.#camelCaseToUpperCase(envNamePrefix); 51 | this.#baseUrl = this.#getKeyFromEnv('url'); 52 | this.#redirectUrl = this.#getKeyFromEnv('redirectUrl'); 53 | this.#username = this.#getKeyFromEnv('username'); 54 | this.#password = this.#getKeyFromEnv('password'); 55 | this.#sfdxLoginUrl = this.#getKeyFromEnv('loginUrl'); 56 | this.#accountId = this.#getKeyFromEnv('accountId'); 57 | this.#contactId = this.#getKeyFromEnv('contactId'); 58 | 59 | // Halt process if Salesforce session timed out 60 | if (this.#envPrefix === SCRATCH_ORG_PREFIX) { 61 | const loginTimestamp = this.#getKeyFromEnv('loginTimestamp'); 62 | if (!loginTimestamp || new Date().getTime() - parseInt(loginTimestamp, 10) > SESSION_TIMEOUT) { 63 | console.error('ERROR: Salesforce session timed out. Re-authenticate before running tests.'); 64 | process.exit(-1); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Read the value of a given key in environment variables 71 | * @param {string} keyName name of the key property as defined in the class constructor 72 | * @returns {string} the key value if found, otherwise the emtpy string 73 | */ 74 | #getKeyFromEnv(keyName) { 75 | const key = `${this.#getEnvKeyName(keyName)}`; 76 | return process.env[key] || ''; 77 | } 78 | 79 | /** 80 | * Transform a key name so that it matches environment variables format. 81 | * @param {string} keyName name of the key property as defined in the class constructor 82 | * @returns {string} the key that matches the property in environment variables 83 | */ 84 | #getEnvKeyName(keyName) { 85 | return `${this.#envPrefix}_${this.#camelCaseToUpperCase(keyName)}`; 86 | } 87 | 88 | /** 89 | * Transform a camelCase string to uppercase separated by underscores 90 | * @param {string} keyName name of the key property as defined in the class constructor 91 | * @returns {string} return the key with the format that matches environment variables (without prefix) 92 | */ 93 | #camelCaseToUpperCase(keyName) { 94 | return keyName 95 | .split(/(?=[A-Z])/) 96 | .join('_') 97 | .toUpperCase(); 98 | } 99 | 100 | /** 101 | * Returns test environment base URL as specified in the .env file 102 | */ 103 | get baseUrl() { 104 | if (!this.#baseUrl) { 105 | throw new Error(`Property ${this.#getEnvKeyName('url')} is not set in '.env' property file`); 106 | } 107 | return this.#baseUrl; 108 | } 109 | 110 | /** 111 | * Returns test environment redirection URL as specified in the .env file 112 | */ 113 | get redirectUrl() { 114 | if (!this.#redirectUrl) { 115 | return this.#baseUrl; 116 | } 117 | return this.#redirectUrl; 118 | } 119 | 120 | /** 121 | * Returns test environment username as specified in the .env file 122 | */ 123 | get username() { 124 | if (!this.#username) { 125 | throw new Error(`Property ${this.#getEnvKeyName('username')} is not set in '.env' property file`); 126 | } 127 | return this.#username; 128 | } 129 | 130 | /** 131 | * Returns test environment password as specified in the .env file 132 | */ 133 | get password() { 134 | if (!this.#password) { 135 | throw new Error(`Property ${this.#getEnvKeyName('password')} is not set in '.env' property file`); 136 | } 137 | return this.#password; 138 | } 139 | 140 | /** 141 | * Returns test environment login URL for scratch orgs 142 | */ 143 | get sfdxLoginUrl() { 144 | if (!this.#sfdxLoginUrl) { 145 | throw new Error(`Property ${this.#getEnvKeyName('loginUrl')} is not set in '.env' property file`); 146 | } 147 | return this.#sfdxLoginUrl; 148 | } 149 | 150 | /** 151 | * Returns account ID for record update test 152 | */ 153 | get accountId() { 154 | if (!this.#accountId) { 155 | throw new Error(`Property ${this.#getEnvKeyName('accountId')} is not set in '.env' property file`); 156 | } 157 | return this.#accountId; 158 | } 159 | 160 | /** 161 | * Returns contact ID for record update test 162 | */ 163 | get contactId() { 164 | if (!this.#accountId) { 165 | throw new Error(`Property ${this.#getEnvKeyName('contactId')} is not set in '.env' property file`); 166 | } 167 | return this.#contactId; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /image.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "elementNames": { 3 | "div": "title", 4 | "span": "titleText" 5 | }, 6 | "methodNames": { 7 | "getSpanText": "getTitleTextContent" 8 | }, 9 | "methodDescriptions": { 10 | "getSpanText": "get text for my field" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/node18/tsconfig.json", "@tsconfig/strictest"], 3 | "include": ["force-app/test/**/*"], 4 | "exclude": ["node_modules", "utam-preview", "pageObjects"], 5 | "compilerOptions": { 6 | "noEmit": true, 7 | "types": ["node", "@wdio/globals/types", "@wdio/jasmine-framework", "wdio-utam-service"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utam-js-recipes", 3 | "license": "MIT", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "lint": "eslint .", 8 | "format": "prettier --write .", 9 | "test": "wdio", 10 | "build": "yarn compile:utam && yarn generate:utam", 11 | "compile:utam": "utam -c utam.config.js && cd utam-preview && yarn compile", 12 | "generate:utam": "cd utam-generator && yarn generate && yarn compile", 13 | "create:env": "node scripts/create-env-file.js", 14 | "generate:login": "node scripts/generate-login-url.js", 15 | "prepare": "husky install" 16 | }, 17 | "devDependencies": { 18 | "@babel/cli": "^7.23.4", 19 | "@babel/core": "^7.23.7", 20 | "@babel/eslint-parser": "^7.23.3", 21 | "@babel/preset-env": "^7.23.7", 22 | "@babel/register": "^7.23.7", 23 | "@lwc/eslint-plugin-lwc": "^1.7.2", 24 | "@salesforce/eslint-config-lwc": "^3.5.2", 25 | "@salesforce/eslint-plugin-lightning": "^1.0.0", 26 | "@salesforce/sfdx-lwc-jest": "^3.1.0", 27 | "@tsconfig/node18": "^18.2.2", 28 | "@tsconfig/strictest": "^2.0.2", 29 | "@types/jasmine": "5.1.4", 30 | "@wdio/appium-service": "^8.27.0", 31 | "@wdio/cli": "^8.27.1", 32 | "@wdio/jasmine-framework": "^8.27.0", 33 | "@wdio/local-runner": "^8.27.0", 34 | "@wdio/spec-reporter": "^8.27.0", 35 | "dotenv": "^16.3.1", 36 | "envfile": "^7.1.0", 37 | "eslint": "^8.56.0", 38 | "eslint-config-prettier": "^9.1.0", 39 | "eslint-plugin-import": "^2.29.1", 40 | "eslint-plugin-jasmine": "^4.1.3", 41 | "eslint-plugin-jest": "^27.6.1", 42 | "eslint-plugin-wdio": "^8.24.12", 43 | "husky": "^8.0.3", 44 | "isbinaryfile": "^5.0.0", 45 | "lint-staged": "^15.2.2", 46 | "prettier": "3.1.1", 47 | "salesforce-pageobjects": "^8.0.0", 48 | "wdio-utam-service": "^3.2.2" 49 | }, 50 | "volta": { 51 | "node": "20.9.0" 52 | }, 53 | "engines": { 54 | "node": ">= 18" 55 | }, 56 | "packageManager": "yarn@4.5.1", 57 | "workspaces": [ 58 | "./", 59 | "utam-preview", 60 | "utam-generator" 61 | ], 62 | "lint-staged": { 63 | "*.{js,css,md}": "prettier --write", 64 | "*.js": "eslint" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /scripts/check-license-headers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | // Borrowed from https://github.com/facebook/jest 9 | 10 | /** 11 | * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. 12 | * 13 | * This source code is licensed under the MIT license found in the 14 | * LICENSE file in the root directory of this source tree. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const fs = require('fs'); 20 | const { execSync } = require('child_process'); 21 | const { isBinaryFileSync } = require('isbinaryfile'); 22 | 23 | const getFileContents = (path) => fs.readFileSync(path, { encoding: 'utf-8' }); 24 | const isDirectory = (path) => fs.lstatSync(path).isDirectory(); 25 | const createRegExp = (pattern) => new RegExp(pattern); 26 | 27 | const IGNORED_EXTENSIONS = [ 28 | 'lock', 29 | 'patch', 30 | 'exe', 31 | 'bin', 32 | 'cfg', 33 | 'config', 34 | 'conf', 35 | // 'html', // might have LWC components we want the header on 36 | 'md', 37 | 'markdown', 38 | 'opam', 39 | 'osm', 40 | 'descr', 41 | 'rst', 42 | 'json', 43 | 'key', 44 | 'ini', 45 | 'plist', 46 | 'snap', 47 | 'svg', 48 | 'txt', 49 | 'xcodeproj', 50 | 'xcscheme', 51 | 'xml', 52 | 'yaml', 53 | 'yml', 54 | 'textile', 55 | 'tsv', 56 | 'csv', 57 | 'pem', 58 | 'csr', 59 | 'der', 60 | 'crt', 61 | 'cert', 62 | 'cer', 63 | 'p7b', 64 | 'iml', 65 | 'org', 66 | 'podspec', 67 | 'modulemap', 68 | 'pch', 69 | 'lproj', 70 | 'xcworkspace', 71 | 'storyboard', 72 | 'tvml', 73 | 'xib', 74 | 'pbxproj', 75 | 'xcworkspacedata', 76 | 'xccheckout', 77 | 'xcsettings', 78 | 'strings', 79 | 'ipynb', 80 | 'htm', 81 | 'toml', 82 | ].map((extension) => createRegExp(`.${extension}$`)); 83 | 84 | const GENERIC_IGNORED_PATTERNS = [ 85 | '(^|/)\\.[^/]+(/|$)', 86 | 87 | //'third[_\\-. ]party/', // to be on the safe side 88 | '^node[_\\-. ]modules/', 89 | 'gradlew\\.bat$', 90 | 'gradlew$', 91 | 'gradle/wrapper/', 92 | '.idea/', 93 | '__init__\\.py$', 94 | '^Setup.hs$', 95 | '^(readme|README|Readme)\\..*$', 96 | 'Cargo\\.toml$', 97 | '^Cartfile.*$', 98 | '^.*\\.xcodeproj/$', 99 | '^.*\\.xcworkspace/$', 100 | '^.*\\.lproj/$', 101 | '^.*\\.bundle/$', 102 | '^MANIFEST\\.in$', 103 | ].map(createRegExp); 104 | 105 | const CUSTOM_IGNORED_PATTERNS = [ 106 | // add anything repo specific here 107 | 'babel.config.js', 108 | '/fixtures/', 109 | '/integration-tests/src/(.(?!.*.spec.js$))*$', 110 | '/integration-karma/test/.*$', 111 | '/integration-karma/test-hydration/.*$', 112 | ].map(createRegExp); 113 | 114 | const IGNORED_PATTERNS = [...IGNORED_EXTENSIONS, ...GENERIC_IGNORED_PATTERNS, ...CUSTOM_IGNORED_PATTERNS]; 115 | 116 | const INCLUDED_PATTERNS = [ 117 | // Any file with an extension 118 | /\.[^/]+$/, 119 | ]; 120 | 121 | const COPYRIGHT_HEADER_RE = /Copyright (\(c\))? [0-9]{4}, (s|S)alesforce.com, inc./; 122 | 123 | function needsCopyrightHeader(file) { 124 | const contents = getFileContents(file); 125 | return contents.trim().length > 0 && !COPYRIGHT_HEADER_RE.test(contents); 126 | } 127 | 128 | function check() { 129 | const allFiles = execSync('git ls-files', { encoding: 'utf-8' }).trim().split('\n'); 130 | 131 | const invalidFiles = allFiles.filter( 132 | (file) => 133 | INCLUDED_PATTERNS.some((pattern) => pattern.test(file)) && 134 | !IGNORED_PATTERNS.some((pattern) => pattern.test(file)) && 135 | !isDirectory(file) && 136 | !isBinaryFileSync(file) && 137 | needsCopyrightHeader(file), 138 | ); 139 | 140 | if (invalidFiles.length > 0) { 141 | console.log(`Salesforce copyright header check failed for the following files: 142 | ${invalidFiles.join('\n ')} 143 | Please include the header or add an exception for the file in \`scripts/checkCopyrightHeaders.js\``); 144 | process.exit(1); 145 | } 146 | } 147 | 148 | check(); 149 | -------------------------------------------------------------------------------- /scripts/create-env-file.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const { writeFileSync } = require('fs'); 11 | const { getDefaultTemplate, DOTENV_FILEPATH } = require('./script-utils'); 12 | 13 | function createDotenvFile() { 14 | const template = getDefaultTemplate(); 15 | writeFileSync(DOTENV_FILEPATH, template); 16 | console.log(`Property .env file successfully generated in ${DOTENV_FILEPATH}`); 17 | } 18 | 19 | createDotenvFile(); 20 | -------------------------------------------------------------------------------- /scripts/generate-login-url.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const util = require('util'); 11 | const exec = util.promisify(require('child_process').exec); 12 | const { existsSync, readFileSync, writeFileSync } = require('fs'); 13 | const { 14 | upsertKeyValue, 15 | getDefaultTemplate, 16 | DOTENV_FILEPATH, 17 | SCRATCH_ORG_KEY, 18 | SCRATCH_ORG_TIMESTAMP, 19 | } = require('./script-utils'); 20 | 21 | /** 22 | * Updates an existing property file and returns its content 23 | * 24 | * @param {string} filePath path the default property file 25 | * @param {string} url scratch org login url to set in the property file 26 | * @param {number} timestamp scratch org login timestamp to set in the property file 27 | * @returns {string} the property file with the updated url 28 | */ 29 | function getUpdatedEnvFile(filePath, url, timestamp) { 30 | let envFile = readFileSync(filePath, { encoding: 'utf-8' }); 31 | envFile = upsertKeyValue(envFile, SCRATCH_ORG_KEY, url); 32 | envFile = upsertKeyValue(envFile, SCRATCH_ORG_TIMESTAMP, timestamp); 33 | return envFile; 34 | } 35 | 36 | /** 37 | * Get the scratch org login url from a child CLI process and parse it 38 | * @returns {Promise} the scratch org url fetched from the getUrlCmd 39 | */ 40 | async function getScratchOrgLoginUrl() { 41 | const getUrlCmd = 'sfdx force:org:open -p /lightning -r --json'; 42 | console.log('Executing the following command: ', getUrlCmd); 43 | const { stderr, stdout } = await exec(getUrlCmd, { cwd: __dirname }); 44 | if (stderr) throw new Error(stderr); 45 | const response = JSON.parse(stdout); 46 | const { url } = response.result; 47 | console.log(`Command returned with response: ${url}`); 48 | return url; 49 | } 50 | 51 | /** 52 | * Main script entry point - generate a property file with the correct scratch org login url: 53 | * 54 | * 1. get the scratch org login url 55 | * 2. create or update the property file with the url returned in step 1 56 | */ 57 | async function generateLoginUrl() { 58 | try { 59 | const url = await getScratchOrgLoginUrl(); 60 | const timestamp = new Date().getTime(); 61 | const envFile = existsSync(DOTENV_FILEPATH) 62 | ? getUpdatedEnvFile(DOTENV_FILEPATH, url, timestamp) 63 | : getDefaultTemplate(url, timestamp); 64 | writeFileSync(DOTENV_FILEPATH, envFile); 65 | console.log(`Property .env file successfully generated in ${DOTENV_FILEPATH}`); 66 | } catch (err) { 67 | console.log(err); 68 | } 69 | } 70 | 71 | generateLoginUrl(); 72 | -------------------------------------------------------------------------------- /scripts/script-utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const { join } = require('path'); 11 | const { parse } = require('envfile'); 12 | 13 | const SCRATCH_ORG_KEY = 'SCRATCH_ORG_LOGIN_URL'; 14 | const SCRATCH_ORG_TIMESTAMP = 'SCRATCH_ORG_LOGIN_TIMESTAMP'; 15 | 16 | const DOTENV_FILEPATH = join(__dirname, '../.env'); 17 | 18 | const ENV_REGEX = /^([a-z0-9_-]*)=(.*)$/gim; 19 | 20 | /** 21 | * Replaces the value of a target key in a property file. 22 | * Creates the key if it does not exist. 23 | * 24 | * @param {string} envFile content of the existing property file 25 | * @param {string} targetKey target key for update 26 | * @param {string} newValue new value for target key 27 | * @returns the content of the updated property file 28 | */ 29 | function upsertKeyValue(envFile, targetKey, newValue) { 30 | const parsedEnv = parse(envFile); 31 | if (Object.prototype.hasOwnProperty.call(parsedEnv, targetKey)) { 32 | // Replace key value 33 | return envFile.replace(ENV_REGEX, (match, key, value) => { 34 | return key === targetKey ? `${key}=${newValue}` : `${key}=${value}`; 35 | }); 36 | } else { 37 | // Append key to file 38 | return `${envFile}\n${targetKey}=${newValue}`; 39 | } 40 | } 41 | 42 | /** 43 | * Generate default content for a property file that can be use to login in the scratch org 44 | * 45 | * @param {string} url scratch org login url to set in the property file 46 | * @param {number} timestamp scratch org login timestamp to set in the property file 47 | * @returns {string} the content of the property file to write to disk 48 | */ 49 | function getDefaultTemplate(url = '', timestamp = null) { 50 | return `# DO NOT CHECK THIS FILE IN WITH PERSONAL INFORMATION SAVED 51 | # 52 | # Environment variables required to run tests. Values here will be populated by 53 | # running "node scripts/generate-login-url.js" 54 | # 55 | # Example: 56 | # SCRATCH_ORG_LOGIN_URL=https://.cs22.my.salesforce.com/secur/frontdoor.jsp?sid= 57 | 58 | SCRATCH_ORG_LOGIN_URL=${url} 59 | SCRATCH_ORG_LOGIN_TIMESTAMP=${timestamp}`; 60 | } 61 | 62 | module.exports = { 63 | SCRATCH_ORG_KEY, 64 | SCRATCH_ORG_TIMESTAMP, 65 | DOTENV_FILEPATH, 66 | upsertKeyValue, 67 | getDefaultTemplate, 68 | }; 69 | -------------------------------------------------------------------------------- /sfdc_metadata/cred_scan_triage/triage.yaml: -------------------------------------------------------------------------------- 1 | clean: true 2 | mock_secret: 3 | - id: utam-js-recipes://README.md_ce4a4e2583530c9507ee303de950441d048fdb50 4 | justification: example in documentation 5 | - id: utam-js-recipes://README.md_1e975b74469bdda630e07c4a3a61f28e512aba17 6 | justification: example in documentation 7 | false_positive: [] 8 | non_production_secret: [] 9 | production_secret: [] 10 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "force-app", 5 | "default": true, 6 | "package": "utam example", 7 | "versionName": "ver 0.1", 8 | "versionNumber": "0.1.1.NEXT" 9 | } 10 | ], 11 | "namespace": "", 12 | "sfdcLoginUrl": "https://login.salesforce.com", 13 | "sourceApiVersion": "50.0" 14 | } 15 | -------------------------------------------------------------------------------- /utam-generator/README.md: -------------------------------------------------------------------------------- 1 | # utam-generator 2 | 3 | UTAM Generator generates JSON UTAM Page Objects from HTML files. 4 | 5 | Documentation is available on [utam.dev](https://utam.dev/tools/generator-start). 6 | 7 | ## Generate page objects 8 | 9 | > To setup this repository please follow instructions in [root README](https://github.com/salesforce/utam-js#readme) 10 | 11 | - Create new folder under `packages/utam-generator/src` 12 | - Copy HTML files you generate from to the folder. Generator will recursively scan inside the folder, so folder can have any number of subfolders. 13 | - Run generator __from the project root__ 14 | 15 | ```bash 16 | yarn generate:utam 17 | ``` 18 | OR 19 | ```bash 20 | yarn build 21 | ``` 22 | `yarn build` also generates page object whereas `yarn generate:utam` only generates and could run faster. 23 | 24 | - Generated JSON files will be created in the folder configured as `outputDir` in generator.config.json (`__generated__`) 25 | - After generation, JSON files will be compiled by UTAM compiler to validate generated syntax is correct. -------------------------------------------------------------------------------- /utam-generator/compiler.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "pageObjectsFileMask": ["**/*.utam.json"], 3 | "pageObjectsOutputDir": "pageObjects", 4 | "alias": { 5 | "utam-*/": "utam-generator/" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /utam-generator/generator.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputRootDir": "./", 3 | "ignoreFolders": ["**/node_modules/**", "**/build/**"], 4 | "inputFileMask": ["**/cards/*.html"], 5 | "outputFileExtension": ".utam.json", 6 | "relativeOutputDir": false, 7 | "outputDir": "__generated__", 8 | "defaultNamespace": "utam-generator/pageObjects/", 9 | "namespaces": { 10 | "lightning": "utam-lightning/pageObjects/" 11 | }, 12 | "generateCustomContent": false, 13 | "descriptionAuthor": "my team", 14 | "includeClasses": ["slds-image"], 15 | "includeTags": ["if:"] 16 | } 17 | -------------------------------------------------------------------------------- /utam-generator/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | throw new Error(`utam-generator is not runnable`); 9 | -------------------------------------------------------------------------------- /utam-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utam-generator", 3 | "license": "MIT", 4 | "version": "0.0.1", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "type": "commonjs", 9 | "main": "index.js", 10 | "scripts": { 11 | "generate": "utam-generate -c generator.config.json", 12 | "compile": "utam -c compiler.config.json" 13 | }, 14 | "files": [ 15 | "index.js", 16 | "pageObjects/**/*.js", 17 | "pageObjects/**/*.cjs", 18 | "pageObjects/**/*.mjs", 19 | "pageObjects/**/*.d.ts" 20 | ], 21 | "exports": { 22 | ".": "./index.js", 23 | "./pageObjects/*": { 24 | "require": "./pageObjects/*.js", 25 | "import": "./pageObjects/*.mjs" 26 | } 27 | }, 28 | "devDependencies": { 29 | "utam": "^2.2.0" 30 | }, 31 | "volta": { 32 | "extends": "../package.json" 33 | }, 34 | "engines": { 35 | "node": ">= 16" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /utam-generator/src/cards/button.html: -------------------------------------------------------------------------------- 1 | 7 | 19 | -------------------------------------------------------------------------------- /utam-generator/src/cards/icon.html: -------------------------------------------------------------------------------- 1 | 7 | 16 | -------------------------------------------------------------------------------- /utam-generator/src/cards/image.html: -------------------------------------------------------------------------------- 1 | 7 | 18 | -------------------------------------------------------------------------------- /utam-generator/src/cards/image.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "elementNames": { 3 | "div": "title", 4 | "span": "titleText" 5 | }, 6 | "methodNames": { 7 | "getSpanText": "getTitleTextContent" 8 | }, 9 | "methodDescriptions": { 10 | "getSpanText": "get text for my field" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /utam-generator/src/cards/text.html: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /utam-generator/src/html/exported.html: -------------------------------------------------------------------------------- 1 | 7 |
8 |
13 | 22 |
23 | 24 |
29 |
30 |
34 |
35 |
36 | 42 | 43 | 74 |
75 | 76 |
80 |
84 | 85 | Henry Chen 88 | 89 | 90 | 91 |
92 |
93 |
    94 |
  • 95 | Software Engineering PMTS 101 |
  • 102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | -------------------------------------------------------------------------------- /utam-generator/src/lwc/input.html: -------------------------------------------------------------------------------- 1 | 7 | 423 | -------------------------------------------------------------------------------- /utam-generator/src/lwc/recordLayoutItem.html: -------------------------------------------------------------------------------- 1 | 7 | 85 | -------------------------------------------------------------------------------- /utam-preview/README.md: -------------------------------------------------------------------------------- 1 | # utam-preview 2 | 3 | Temporary package with Salesforce specific Page Objects. 4 | Exists only for the duration of UTAM pilot. 5 | 6 | - to generate page objects 7 | ```bash 8 | yarn compile 9 | ``` 10 | 11 | - to publish artifact 12 | ```bash 13 | cd utam-preview 14 | npm login 15 | npm npm publish --access public 16 | ``` 17 | 18 | - to preview published artifact 19 | ```bash 20 | npm pack 21 | ``` -------------------------------------------------------------------------------- /utam-preview/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | throw new Error(` 9 | PageObjects are not available directly from this entry point. 10 | Try: "utam-preview/pageObjects/[pageObjectName]" 11 | `); 12 | -------------------------------------------------------------------------------- /utam-preview/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utam-preview", 3 | "license": "MIT", 4 | "version": "0.0.1", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "type": "commonjs", 9 | "main": "index.js", 10 | "scripts": { 11 | "compile": "utam -c utam-preview.config.js" 12 | }, 13 | "files": [ 14 | "index.js", 15 | "pageObjects/**/*.js", 16 | "pageObjects/**/*.cjs", 17 | "pageObjects/**/*.mjs", 18 | "pageObjects/**/*.d.ts" 19 | ], 20 | "exports": { 21 | ".": "./index.js", 22 | "./pageObjects/*": { 23 | "require": "./pageObjects/*.js", 24 | "import": "./pageObjects/*.mjs" 25 | }, 26 | "./utils/*": { 27 | "require": "./utils/*.js", 28 | "import": "./utils/*.mjs" 29 | } 30 | }, 31 | "devDependencies": { 32 | "utam": "^2.2.0" 33 | }, 34 | "volta": { 35 | "extends": "../package.json" 36 | }, 37 | "engines": { 38 | "node": ">= 16" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /utam-preview/src/portal/dummy.utam.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "selector": { 4 | "css": "notexisting" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /utam-preview/src/portal/nullableExample.utam.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "selector": { 4 | "css": "body" 5 | }, 6 | "elements": [ 7 | { 8 | "name": "nullableBasicElement", 9 | "selector": { 10 | "css": ".idontexist" 11 | }, 12 | "public": true, 13 | "nullable": true, 14 | "elements": [ 15 | { 16 | "name": "scopedInsideNullable", 17 | "selector": { 18 | "css": ".idontexist" 19 | }, 20 | "public": true, 21 | "nullable": true 22 | } 23 | ] 24 | }, 25 | { 26 | "name": "nullableBasicElementList", 27 | "selector": { 28 | "css": ".idontexist", 29 | "returnAll": true 30 | }, 31 | "public": true, 32 | "nullable": true 33 | }, 34 | { 35 | "name": "nullableCustomElement", 36 | "type": "utam-portal/pageObjects/dummy", 37 | "selector": { 38 | "css": ".idontexist" 39 | }, 40 | "public": true, 41 | "nullable": true 42 | }, 43 | { 44 | "name": "nullableCustomElementList", 45 | "type": "utam-portal/pageObjects/dummy", 46 | "selector": { 47 | "css": ".idontexist", 48 | "returnAll": true 49 | }, 50 | "public": true, 51 | "nullable": true 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /utam-preview/src/portal/utamDevHome.utam.json: -------------------------------------------------------------------------------- 1 | { 2 | "selector": { 3 | "css": "body" 4 | }, 5 | "root": true, 6 | "elements": [ 7 | { 8 | "name": "persistentHeader", 9 | "selector": { 10 | "css": "header" 11 | }, 12 | "elements": [ 13 | { 14 | "name": "menu", 15 | "selector": { 16 | "css": ".menu" 17 | }, 18 | "elements": [ 19 | { 20 | "public": true, 21 | "name": "grammarMenuItem", 22 | "selector": { 23 | "css": "[data-navbar-id='grammar']" 24 | }, 25 | "type": "clickable" 26 | }, 27 | { 28 | "public": true, 29 | "type": "clickable", 30 | "name": "menuItems", 31 | "selector": { 32 | "css": "li", 33 | "returnAll": true 34 | } 35 | } 36 | ] 37 | } 38 | ] 39 | }, 40 | { 41 | "name": "content", 42 | "public": true, 43 | "selector": { 44 | "css": "main.content" 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /utam-preview/utam-preview.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | module.exports = { 9 | // file mask for utam page objects 10 | pageObjectsFileMask: ['src/**/*.utam.json'], 11 | // output folder for generated page objects, relative to the package root 12 | pageObjectsOutputDir: 'pageObjects', 13 | // remap custom elements imports 14 | alias: { 15 | 'utam-*/': 'utam-preview/', 16 | }, 17 | lint: { 18 | printToConsole: false, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /utam.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | module.exports = { 9 | // file masks for utam page objects 10 | pageObjectsFileMask: ['force-app/**/__utam__/**/*.utam.json'], 11 | // output folder for generated page objects, relative to the package root 12 | pageObjectsOutputDir: 'pageObjects', 13 | // remap custom elements imports 14 | alias: { 15 | 'utam-sfdx/': 'utam-js-recipes/', 16 | 'utam-*/': 'utam-preview/', 17 | }, 18 | lint: { 19 | printToConsole: false, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /wdio.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | require('dotenv').config(); 9 | 10 | const { UtamWdioService } = require('wdio-utam-service'); 11 | // use prefix 'DEBUG=true' to run test in debug mode 12 | const { DEBUG } = process.env; 13 | const EXPLICIT_TIMEOUT = 60 * 1000; 14 | const DEBUG_TIMEOUT = EXPLICIT_TIMEOUT * 30; 15 | 16 | exports.config = { 17 | runner: 'local', 18 | specs: ['force-app/test/**/*.spec.js'], 19 | maxInstances: 1, 20 | capabilities: [ 21 | { 22 | maxInstances: 1, 23 | browserName: 'chrome', 24 | browserVersion: '120.0.6099.62', 25 | }, 26 | ], 27 | logLevel: 'debug', 28 | bail: 0, 29 | // timeout for all waitFor commands 30 | waitforTimeout: DEBUG ? DEBUG_TIMEOUT : EXPLICIT_TIMEOUT, 31 | connectionRetryTimeout: 120000, 32 | connectionRetryCount: 3, 33 | automationProtocol: 'webdriver', 34 | services: [ 35 | [ 36 | UtamWdioService, 37 | { 38 | implicitTimeout: 0, 39 | injectionConfigs: ['salesforce-pageobjects/ui-global-components.config.json'], 40 | }, 41 | ], 42 | ], 43 | framework: 'jasmine', 44 | reporters: ['spec'], 45 | jasmineOpts: { 46 | // max execution time for a script, set to 5 min 47 | defaultTimeoutInterval: 1000 * 60 * 5, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /wdio.conf.mobilebase.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | require('dotenv').config(); 9 | 10 | const EXPLICIT_TIMEOUT = 60 * 1000; 11 | const DEBUG_TIMEOUT = EXPLICIT_TIMEOUT * 30; 12 | const { DEBUG } = process.env; 13 | 14 | exports.mobileBaseConfig = { 15 | // 16 | // ==================== 17 | // Runner Configuration 18 | // ==================== 19 | // 20 | // WebdriverIO allows it to run your tests in arbitrary locations (e.g. locally or 21 | // on a remote machine). 22 | runner: 'local', 23 | port: 4444, 24 | 25 | // 26 | // ============ 27 | // Capabilities 28 | // ============ 29 | // Define your capabilities here. WebdriverIO can run multiple capabilities at the same 30 | // time. Depending on the number of capabilities, WebdriverIO launches several test 31 | // sessions. Within your capabilities you can overwrite the spec and exclude options in 32 | // order to group specific specs to a specific capability. 33 | 34 | maxInstances: 1, 35 | logLevel: 'info', 36 | bail: 0, 37 | baseUrl: 'http://localhost', 38 | // timeout for all waitFor commands 39 | waitforTimeout: DEBUG ? DEBUG_TIMEOUT : EXPLICIT_TIMEOUT, 40 | connectionRetryTimeout: 120000, 41 | connectionRetryCount: 3, 42 | framework: 'jasmine', 43 | reporters: ['spec'], 44 | jasmineOpts: { 45 | // max execution time for a script, set to 5 min 46 | defaultTimeoutInterval: 1000 * 60 * 5, 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /wdio.conf.mpcc.android.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | const { mpccBaseConfig } = require('./wdio.conf.mpccbase'); 9 | 10 | exports.config = { 11 | ...mpccBaseConfig, 12 | capabilities: [ 13 | { 14 | platformName: 'Android', 15 | 'appium:orientation': 'PORTRAIT', 16 | 'appium:automationName': 'UiAutomator2', 17 | 'appium:deviceName': 'emulator-5554', 18 | // TODO: replace with the test application path in your local 19 | 'appium:app': '', 20 | 'appium:appActivity': 'com.mysalesforce.community.startgate.StartActivity', 21 | 'appium:appPackage': 'com.mysalesforce.mycommunity.playgroundcommunity', 22 | 'appium:newCommandTimeout': 240, 23 | }, 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /wdio.conf.mpcc.ios.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | const { mpccBaseConfig } = require('./wdio.conf.mpccbase'); 9 | 10 | exports.config = { 11 | ...mpccBaseConfig, 12 | capabilities: [ 13 | { 14 | platformName: 'iOS', 15 | 'appium:autoWebview': true, 16 | 'appium:deviceName': 'iPhone 12', 17 | // TODO: replace with the test application path in your local 18 | 'appium:app': '', 19 | 'appium:automationName': 'XCUITest', 20 | 'appium:platformVersion': '15.2', 21 | }, 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /wdio.conf.mpccbase.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | const { mobileBaseConfig } = require('./wdio.conf.mobilebase'); 8 | const { UtamWdioService } = require('wdio-utam-service'); 9 | 10 | exports.mpccBaseConfig = { 11 | ...mobileBaseConfig, 12 | specs: ['force-app/test/mobile/playgroundapp/*.spec.js'], 13 | services: [ 14 | [ 15 | 'appium', 16 | { 17 | command: 'appium', 18 | }, 19 | ], 20 | [ 21 | UtamWdioService, 22 | { 23 | implicitTimeout: 0, 24 | injectionConfigs: ['salesforce-pageobjects/utam-communities-pageobjects.config.json'], 25 | }, 26 | ], 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /wdio.conf.sapp.android.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | const { sappBaseConfig } = require('./wdio.conf.sappbase'); 9 | 10 | exports.config = { 11 | ...sappBaseConfig, 12 | capabilities: [ 13 | { 14 | platformName: 'Android', 15 | 'appium:orientation': 'PORTRAIT', 16 | 'appium:automationName': 'UiAutomator2', 17 | 'appium:deviceName': 'emulator-5554', 18 | // TODO: replace with the test application path in your local 19 | 'appium:app': '', 20 | 'appium:appActivity': 'com.salesforce.chatter.Chatter', 21 | 'appium:appPackage': 'com.salesforce.chatter', 22 | 'appium:newCommandTimeout': 240, 23 | }, 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /wdio.conf.sapp.ios.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | 8 | const { sappBaseConfig } = require('./wdio.conf.sappbase'); 9 | 10 | exports.config = { 11 | ...sappBaseConfig, 12 | capabilities: [ 13 | { 14 | platformName: 'iOS', 15 | 'appium:autoWebview': true, 16 | 'appium:deviceName': 'iPhone 12', 17 | // TODO: replace with the test application path in your local 18 | 'appium:app': '', 19 | 'appium:automationName': 'XCUITest', 20 | 'appium:platformVersion': '15.2', 21 | }, 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /wdio.conf.sappbase.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | const { mobileBaseConfig } = require('./wdio.conf.mobilebase'); 8 | const { UtamWdioService } = require('wdio-utam-service'); 9 | 10 | exports.sappBaseConfig = { 11 | ...mobileBaseConfig, 12 | specs: ['force-app/test/mobile/salesforceapp/*.spec.js'], 13 | services: [ 14 | [ 15 | 'appium', 16 | { 17 | command: 'appium', 18 | }, 19 | ], 20 | [ 21 | UtamWdioService, 22 | { 23 | implicitTimeout: 0, 24 | injectionConfigs: ['salesforce-pageobjects/utam-salesforceapp-pageobjects.config.json'], 25 | }, 26 | ], 27 | ], 28 | }; 29 | --------------------------------------------------------------------------------