Beta versions exist to allow you to make experimental changes in your managed package. These changes get "baked in" to the package, in an irreversible way**, when you include them in a managed release (for 2GP, when you promote a beta that includes the changes).
**: with 2GP you can create "branches" of your package, and those different branches may contain entirely different changes. Of course it is not possible to upgrade an installed version from one branch with a version on a different branch, so effectively resulting in much the same behaviour as per 1GP beta releases.
Installed betas cannot be upgraded because the changes in them may not be included in any other version. This also applies to cross-branch scenarios, as above.
However, for testing purposes, it is best to use betas since you may discover significant design issues that require a change that cannot be done in a compatible manner.
Such beta testing works best if you step away from the concept of fixed orgs for staging/testing and adopt scratch orgs. As with scratch org based best practice development, you create a scratch org for each version you want to test with. Unlike dev scratch orgs, it is necessary to use an unnamespaced scratch org so as to mimic a subscriber org.
Since you use a scratch org (with org shape and/or scripting to help you finalise org configuration, including data setup), you never need to upgrade a beta - you simply create a new scratch org with the alternative version.
The key is then that you only promote versions that have been tested and that would go towards production.
Patch versions can be treated identically, using betas initially and subsequently promoting the fix version.
NB: Patching 2GPs requires the package to have passed security review, since Salesforce Support will only enable this capability once it has been through that review process.
Here's an outline of how we work with package development:
- We use namespaced scratch orgs on a per-developer, per-task basis. These scratch orgs are created to last at most the length of the development sprint. We do this to:
- Allow us to deploy/push the metadata from the task's related git branch quickly and easily.
- Ensure that the scratch org is clean, only including the metadata from the task's related git branch. This avoids cruft being on the org that could affect behaviour and that isn't in the git branch.
- Be compatible with some of the oddities around certain metadata (I'm looking at you, Flow, and you, Aura) that require explicit namespace references without the need to mangle the metadata during deployment.
- We use namespaced scratch orgs for QA for each task. We do this to:
- Ensure the org contains just the metadata from the git branch to be tested.
- Avoid the need to create beta releases of the product for every task. We just deploy/push the metadata.
- We use no-namespace scratch orgs for QA regression testing before release of a new version. Here, we:
- Use beta releases (of the new version or a patch, as relevant) and install these on the org.
- Can ensure that we don't have unexpected behaviour for packaged metadata compared with deployed metadata, in an environment that is closer to a production org in structure and content.
To support this, we created some tooling including:
- Scratch org creation and metadata population, including some "unpackaged metadata" (such as perm sets and some testing/demo configuration).
- Test data import/export plugin for the SF CLI. This allows QA to have specific data for their manual tests.
- JEST UI component testing.
- UTAM UI end-to-end testing.
- Demo data generation for randomized exploratory testing or quick demos.
Only once we have developed and QAed individual tasks do we then create a beta release for regression testing. Only after we pass regression testing do we promote for version release.