From d97f7756f6a522ea3b4d851a2542b28cf446c05d Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 10 Sep 2026 19:12:55 +0530 Subject: [PATCH 1/2] Add UI Scripts library for Test Management Add a library of UI Script recipes for BrowserStack Test Management, organized by surface: add-result, test-case-form, test-case-results-toolbar, test-run-form, test-plan-form, and exploratory-session-form. Includes a README documenting usage, roles, and the folder map. Co-Authored-By: Claude Opus 4.8 --- ui-scripts/README.md | 116 ++++++++++++++++++ ...uire-a-linked-defect-on-a-failed-result.js | 11 ++ .../require-notes-on-a-failed-result.js | 21 ++++ .../combine-rules-on-the-session-form.js | 34 +++++ .../enforce-a-session-name-convention.js | 9 ++ .../limit-the-session-timebox.js | 14 +++ .../lock-a-field-on-the-session-form.js | 5 + .../pre-fill-defaults-on-the-session-form.js | 10 ++ ...equire-a-dropdown-when-a-checkbox-is-on.js | 7 ++ ...down-when-a-text-field-says-automatable.js | 8 ++ ...quire-a-linked-requirement-on-a-session.js | 9 ++ ...show-or-hide-fields-on-the-session-form.js | 11 ++ .../add-a-form-button-that-opens-a-dialog.js | 17 +++ .../add-a-form-button-that-opens-a-link.js | 10 ++ .../build-a-dialog-with-your-own-buttons.js | 23 ++++ .../enforce-a-title-naming-convention.js | 10 ++ ...-automation-status-and-type-combination.js | 12 ++ ui-scripts/test-case-form/hide-a-field.js | 8 ++ .../hide-or-lock-a-built-in-field.js | 10 ++ .../lock-a-field-against-editing.js | 7 ++ .../make-an-optional-field-required.js | 8 ++ .../pre-fill-a-custom-dropdown.js | 8 ++ ...hen-automation-status-becomes-automated.js | 9 ++ .../require-a-linked-requirement.js | 9 ++ .../require-a-tag-on-critical-test-cases.js | 11 ++ .../require-at-least-one-non-empty-step.js | 14 +++ ...quire-one-custom-field-based-on-another.js | 16 +++ .../test-case-form/require-preconditions.js | 11 ++ .../restrict-the-options-on-a-dropdown.js | 8 ++ ...-a-field-only-when-priority-is-critical.js | 17 +++ .../file-a-pre-filled-bug-in-your-tracker.js | 56 +++++++++ .../open-a-runbook-from-the-toolbar.js | 10 ++ ...n-your-own-handler-from-a-dialog-button.js | 22 ++++ .../combine-rules-on-the-test-plan-form.js | 34 +++++ .../enforce-a-test-plan-name-convention.js | 9 ++ .../keep-the-end-date-after-the-start-date.js | 20 +++ .../lock-a-field-on-the-test-plan-form.js | 5 + ...pre-fill-defaults-on-the-test-plan-form.js | 9 ++ ...equire-a-dropdown-when-a-checkbox-is-on.js | 7 ++ ...down-when-a-text-field-says-automatable.js | 8 ++ ...ire-a-linked-requirement-on-a-test-plan.js | 9 ++ ...ow-or-hide-fields-on-the-test-plan-form.js | 11 ++ .../combine-rules-on-the-run-form.js | 28 +++++ .../enforce-a-run-name-convention.js | 10 ++ .../lock-assign-run-and-configurations.js | 8 ++ .../pre-fill-defaults-when-a-form-opens.js | 9 ++ ...field-when-the-title-marks-a-run-urgent.js | 11 ++ .../require-a-test-plan-on-every-run.js | 10 ++ ...st-plan-when-the-title-says-automatable.js | 8 ++ ...field-behaviors-when-the-run-form-opens.js | 15 +++ .../show-or-hide-fields-on-the-run-form.js | 15 +++ 51 files changed, 777 insertions(+) create mode 100644 ui-scripts/README.md create mode 100644 ui-scripts/add-result/require-a-linked-defect-on-a-failed-result.js create mode 100644 ui-scripts/add-result/require-notes-on-a-failed-result.js create mode 100644 ui-scripts/exploratory-session-form/combine-rules-on-the-session-form.js create mode 100644 ui-scripts/exploratory-session-form/enforce-a-session-name-convention.js create mode 100644 ui-scripts/exploratory-session-form/limit-the-session-timebox.js create mode 100644 ui-scripts/exploratory-session-form/lock-a-field-on-the-session-form.js create mode 100644 ui-scripts/exploratory-session-form/pre-fill-defaults-on-the-session-form.js create mode 100644 ui-scripts/exploratory-session-form/require-a-dropdown-when-a-checkbox-is-on.js create mode 100644 ui-scripts/exploratory-session-form/require-a-dropdown-when-a-text-field-says-automatable.js create mode 100644 ui-scripts/exploratory-session-form/require-a-linked-requirement-on-a-session.js create mode 100644 ui-scripts/exploratory-session-form/show-or-hide-fields-on-the-session-form.js create mode 100644 ui-scripts/test-case-form/add-a-form-button-that-opens-a-dialog.js create mode 100644 ui-scripts/test-case-form/add-a-form-button-that-opens-a-link.js create mode 100644 ui-scripts/test-case-form/build-a-dialog-with-your-own-buttons.js create mode 100644 ui-scripts/test-case-form/enforce-a-title-naming-convention.js create mode 100644 ui-scripts/test-case-form/forbid-an-invalid-automation-status-and-type-combination.js create mode 100644 ui-scripts/test-case-form/hide-a-field.js create mode 100644 ui-scripts/test-case-form/hide-or-lock-a-built-in-field.js create mode 100644 ui-scripts/test-case-form/lock-a-field-against-editing.js create mode 100644 ui-scripts/test-case-form/make-an-optional-field-required.js create mode 100644 ui-scripts/test-case-form/pre-fill-a-custom-dropdown.js create mode 100644 ui-scripts/test-case-form/require-a-field-when-automation-status-becomes-automated.js create mode 100644 ui-scripts/test-case-form/require-a-linked-requirement.js create mode 100644 ui-scripts/test-case-form/require-a-tag-on-critical-test-cases.js create mode 100644 ui-scripts/test-case-form/require-at-least-one-non-empty-step.js create mode 100644 ui-scripts/test-case-form/require-one-custom-field-based-on-another.js create mode 100644 ui-scripts/test-case-form/require-preconditions.js create mode 100644 ui-scripts/test-case-form/restrict-the-options-on-a-dropdown.js create mode 100644 ui-scripts/test-case-form/show-a-field-only-when-priority-is-critical.js create mode 100644 ui-scripts/test-case-results-toolbar/file-a-pre-filled-bug-in-your-tracker.js create mode 100644 ui-scripts/test-case-results-toolbar/open-a-runbook-from-the-toolbar.js create mode 100644 ui-scripts/test-case-results-toolbar/run-your-own-handler-from-a-dialog-button.js create mode 100644 ui-scripts/test-plan-form/combine-rules-on-the-test-plan-form.js create mode 100644 ui-scripts/test-plan-form/enforce-a-test-plan-name-convention.js create mode 100644 ui-scripts/test-plan-form/keep-the-end-date-after-the-start-date.js create mode 100644 ui-scripts/test-plan-form/lock-a-field-on-the-test-plan-form.js create mode 100644 ui-scripts/test-plan-form/pre-fill-defaults-on-the-test-plan-form.js create mode 100644 ui-scripts/test-plan-form/require-a-dropdown-when-a-checkbox-is-on.js create mode 100644 ui-scripts/test-plan-form/require-a-dropdown-when-a-text-field-says-automatable.js create mode 100644 ui-scripts/test-plan-form/require-a-linked-requirement-on-a-test-plan.js create mode 100644 ui-scripts/test-plan-form/show-or-hide-fields-on-the-test-plan-form.js create mode 100644 ui-scripts/test-run-form/combine-rules-on-the-run-form.js create mode 100644 ui-scripts/test-run-form/enforce-a-run-name-convention.js create mode 100644 ui-scripts/test-run-form/lock-assign-run-and-configurations.js create mode 100644 ui-scripts/test-run-form/pre-fill-defaults-when-a-form-opens.js create mode 100644 ui-scripts/test-run-form/require-a-field-when-the-title-marks-a-run-urgent.js create mode 100644 ui-scripts/test-run-form/require-a-test-plan-on-every-run.js create mode 100644 ui-scripts/test-run-form/require-a-test-plan-when-the-title-says-automatable.js create mode 100644 ui-scripts/test-run-form/set-several-field-behaviors-when-the-run-form-opens.js create mode 100644 ui-scripts/test-run-form/show-or-hide-fields-on-the-run-form.js diff --git a/ui-scripts/README.md b/ui-scripts/README.md new file mode 100644 index 0000000..d547759 --- /dev/null +++ b/ui-scripts/README.md @@ -0,0 +1,116 @@ +# UI Scripts for BrowserStack Test Management + +A UI Script is JavaScript that an admin attaches to one or more surfaces in Test Management, such as the Create Test Case form or the result dialog. Every project member who opens that surface runs the script in their own browser, so one rule reaches everyone on the project. + +This folder is the UI Script library. Each script is one `.js` file, in a folder named for the surface it runs on. The [UI Scripts documentation](https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts) explains the concepts, the [UI Script examples](https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples) page describes what each surface hands your callback, and the [TM toolkit reference](https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/tm-toolkit-reference) describes every `TM` function the scripts use. + +> **Warning:** Treat every script in this folder as a starting point. Test it in a sandbox project and confirm the behavior before you enable it on a production project. + +## Who can add a UI Script + +Only an admin can configure UI Scripts. You need an IAM role of **Owner** or **Admin**, or the **Product Admin** role in Test Management. Every other role can work in a project that already has UI Scripts, but cannot open the **UI Scripts** page or edit a script. For the full role breakdown, see [Role-Based Access Control](https://www.browserstack.com/docs/test-management/user-access-control). + +## How to use a script + +1. Open the `.js` file for the recipe you want. The comment at the top names the recipe, the surface to select, and what the script does. +2. Replace any placeholder token, such as `` or ``, with your own value. +3. Swap the sample field names for the names your own project uses. Most scripts use names such as **Target Region** and **Automation Link**. The run, plan, and session scripts that came from an engineering test project use names such as **UIS String** and **UIS Boolean**. +4. In Test Management, create a new UI Script, paste the code, select the surface named in the header comment, and save. +5. Reload the app before you test. The script engine loads every active script once, when Test Management loads, so a script you create, edit, enable, disable, or delete takes effect on the next page load. + +## Three rules to know before you enable a script + +- **Return the block.** A save callback blocks the save only when it returns the result of `TM.blockSave`. A call without `return` does nothing and the save goes through. +- **Enabled scripts run together.** Every enabled script on a surface runs, in priority order, and stops at the first script that blocks a save. To test one script on its own, disable the others on that surface. The scripts named "Combine rules" already bundle several behaviors, so do not enable them alongside the single-rule scripts they contain. +- **Trigger on text or boolean fields.** On the run, plan, and session forms, a dropdown hands `TM.onFieldChange` its stored value rather than the visible label. A text or boolean field hands you exactly what the tester entered, so build conditional rules on those. + +## Folder map + +Each folder holds the scripts for one surface. + +| Folder | Surface | Scripts | +| ------ | ------- | ------- | +| `add-result/` | Add Result and Update Result | 2 | +| `test-case-results-toolbar/` | Test Case Results Toolbar | 3 | +| `test-case-form/` | Test Case Form | 18 | +| `test-run-form/` | Test Run Form | 9 | +| `test-plan-form/` | Test Plan Form | 9 | +| `exploratory-session-form/` | Exploratory Session Form | 9 | + +The scripts in `add-result/` register the `add_result` surface in code. Select both the Add Result and Update Result surfaces when you save the script, so the rule applies whether a tester logs a new result or edits an existing one. + +### Add Result and Update Result + +- `require-notes-on-a-failed-result.js` +- `require-a-linked-defect-on-a-failed-result.js` + +### Test Case Results Toolbar + +- `file-a-pre-filled-bug-in-your-tracker.js` +- `open-a-runbook-from-the-toolbar.js` +- `run-your-own-handler-from-a-dialog-button.js` + +### Test Case Form + +Validation: + +- `enforce-a-title-naming-convention.js` +- `require-at-least-one-non-empty-step.js` +- `require-preconditions.js` +- `require-a-linked-requirement.js` +- `require-one-custom-field-based-on-another.js` +- `require-a-tag-on-critical-test-cases.js` +- `forbid-an-invalid-automation-status-and-type-combination.js` + +Field behavior: + +- `lock-a-field-against-editing.js` +- `hide-a-field.js` +- `pre-fill-a-custom-dropdown.js` +- `restrict-the-options-on-a-dropdown.js` +- `show-a-field-only-when-priority-is-critical.js` +- `require-a-field-when-automation-status-becomes-automated.js` +- `hide-or-lock-a-built-in-field.js` +- `make-an-optional-field-required.js` + +Buttons and dialogs: + +- `add-a-form-button-that-opens-a-dialog.js` +- `add-a-form-button-that-opens-a-link.js` +- `build-a-dialog-with-your-own-buttons.js` + +### Test Run Form + +- `enforce-a-run-name-convention.js` +- `require-a-test-plan-on-every-run.js` +- `require-a-test-plan-when-the-title-says-automatable.js` +- `require-a-field-when-the-title-marks-a-run-urgent.js` +- `show-or-hide-fields-on-the-run-form.js` +- `lock-assign-run-and-configurations.js` +- `pre-fill-defaults-when-a-form-opens.js` +- `set-several-field-behaviors-when-the-run-form-opens.js` +- `combine-rules-on-the-run-form.js` + +### Test Plan Form + +- `enforce-a-test-plan-name-convention.js` +- `require-a-linked-requirement-on-a-test-plan.js` +- `require-a-dropdown-when-a-text-field-says-automatable.js` +- `require-a-dropdown-when-a-checkbox-is-on.js` +- `show-or-hide-fields-on-the-test-plan-form.js` +- `lock-a-field-on-the-test-plan-form.js` +- `pre-fill-defaults-on-the-test-plan-form.js` +- `keep-the-end-date-after-the-start-date.js` +- `combine-rules-on-the-test-plan-form.js` + +### Exploratory Session Form + +- `enforce-a-session-name-convention.js` +- `require-a-linked-requirement-on-a-session.js` +- `require-a-dropdown-when-a-text-field-says-automatable.js` +- `require-a-dropdown-when-a-checkbox-is-on.js` +- `show-or-hide-fields-on-the-session-form.js` +- `lock-a-field-on-the-session-form.js` +- `pre-fill-defaults-on-the-session-form.js` +- `limit-the-session-timebox.js` +- `combine-rules-on-the-session-form.js` diff --git a/ui-scripts/add-result/require-a-linked-defect-on-a-failed-result.js b/ui-scripts/add-result/require-a-linked-defect-on-a-failed-result.js new file mode 100644 index 0000000..aa55f15 --- /dev/null +++ b/ui-scripts/add-result/require-a-linked-defect-on-a-failed-result.js @@ -0,0 +1,11 @@ +// Recipe: Require a linked defect on a Failed result +// Surface: Add Result and Update Result (select both) +// A Failed result should point at the defect it produced. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('add_result', function (formState) { + if (formState.status.label === 'Failed' && + (!formState.defects || formState.defects.length === 0)) { + return TM.blockSave('Link at least one defect when the result is Failed.'); + } +}); diff --git a/ui-scripts/add-result/require-notes-on-a-failed-result.js b/ui-scripts/add-result/require-notes-on-a-failed-result.js new file mode 100644 index 0000000..632f628 --- /dev/null +++ b/ui-scripts/add-result/require-notes-on-a-failed-result.js @@ -0,0 +1,21 @@ +// Recipe: Require Notes on a Failed result +// Surface: Add Result and Update Result (select both) +// A tester should not be able to log a Failed result without explaining the failure. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('add_result', function (formState) { + var isEmptyRichText = function (html) { + if (!html) return true; + var text = String(html) + .replace(/<[^>]*>/g, '') + .replace(/ /gi, ' ') + .replace(/&[a-z]+;|&#\d+;/gi, '') + .replace(/\s+/g, '') + .trim(); + return text.length === 0; + }; + + if (formState.status.label === 'Failed' && isEmptyRichText(formState.notes)) { + return TM.blockSave('Notes are required when the result is Failed.'); + } +}); diff --git a/ui-scripts/exploratory-session-form/combine-rules-on-the-session-form.js b/ui-scripts/exploratory-session-form/combine-rules-on-the-session-form.js new file mode 100644 index 0000000..1049ff3 --- /dev/null +++ b/ui-scripts/exploratory-session-form/combine-rules-on-the-session-form.js @@ -0,0 +1,34 @@ +// Recipe: Combine rules on the session form +// Surface: Exploratory Session Form +// Require Timebox, pre-fill the title, shape custom fields, drive Test Plan from URGENT in the title, and enforce the [ES] prefix in one script. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +// onFormLoad - fires once when the form opens +TM.onFormLoad('exploratory_session_form', function (form) { + TM.setFieldRequired('timebox', true); // built-in Timebox shows the required * + TM.setFieldValue('title', '[ES] '); // prefill the session title + TM.setFieldRequired('UIS String', true); // a custom field, by display name + TM.setFieldVisible('UIS Url', false); // hide a custom field + TM.setFieldReadOnly('UIS Integer', true); // custom field input disabled +}); + +// onFieldChange - conditional / bidirectional (a text or boolean trigger is reliable) +// Toggle "UIS Boolean" ON -> Dropdown required, MultiSel hidden. OFF -> revert. +TM.onFieldChange('UIS Boolean', function (val) { + var on = (val === true || val === 'true'); + TM.setFieldRequired('UIS Dropdown', on); + TM.setFieldVisible('UIS MultiSel', !on); +}); + +// onFieldChange on a standard field (title) driving a standard field +// Type "URGENT" anywhere in the Title -> Test Plan becomes required. +TM.onFieldChange('title', function (val) { + var v = (val && val.value !== undefined) ? val.value : (val || ''); + TM.setFieldRequired('testPlan', String(v).indexOf('URGENT') >= 0); +}); + +// onBeforeSave - block with a message (note the RETURN) +TM.onBeforeSave('exploratory_session_form', function (form) { + if (!/^\[ES\]/.test(form.title || '')) { + return TM.blockSave('Title must start with [ES]', 'title'); + } +}); diff --git a/ui-scripts/exploratory-session-form/enforce-a-session-name-convention.js b/ui-scripts/exploratory-session-form/enforce-a-session-name-convention.js new file mode 100644 index 0000000..9c02b25 --- /dev/null +++ b/ui-scripts/exploratory-session-form/enforce-a-session-name-convention.js @@ -0,0 +1,9 @@ +// Recipe: Enforce a session name convention +// Surface: Exploratory Session Form +// Block the save unless the session title starts with [ES]. The error renders inline under the title. + +TM.onBeforeSave('exploratory_session_form', function (form) { + if (!/^\[ES\]/.test(form.title || '')) { + return TM.blockSave('Title must start with [ES]', 'title'); + } +}); diff --git a/ui-scripts/exploratory-session-form/limit-the-session-timebox.js b/ui-scripts/exploratory-session-form/limit-the-session-timebox.js new file mode 100644 index 0000000..fde9188 --- /dev/null +++ b/ui-scripts/exploratory-session-form/limit-the-session-timebox.js @@ -0,0 +1,14 @@ +// Recipe: Limit the session timebox +// Surface: Exploratory Session Form +// This rule keeps the session timebox between 1 and 480 minutes. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('exploratory_session_form', function (form) { + var minutes = Number(form.timebox); + if (!form.timebox || isNaN(minutes) || minutes < 1) { + return TM.blockSave('Timebox must be at least 1 minute.', 'timebox'); + } + if (minutes > 480) { + return TM.blockSave('Timebox cannot exceed 480 minutes.', 'timebox'); + } +}); diff --git a/ui-scripts/exploratory-session-form/lock-a-field-on-the-session-form.js b/ui-scripts/exploratory-session-form/lock-a-field-on-the-session-form.js new file mode 100644 index 0000000..eb7dc34 --- /dev/null +++ b/ui-scripts/exploratory-session-form/lock-a-field-on-the-session-form.js @@ -0,0 +1,5 @@ +// Recipe: Lock a field on the session form +// Surface: Exploratory Session Form +// Disable a custom field when the form opens. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFormLoad('exploratory_session_form', function () { TM.setFieldReadOnly('UIS Integer', true); }); diff --git a/ui-scripts/exploratory-session-form/pre-fill-defaults-on-the-session-form.js b/ui-scripts/exploratory-session-form/pre-fill-defaults-on-the-session-form.js new file mode 100644 index 0000000..4ebbe99 --- /dev/null +++ b/ui-scripts/exploratory-session-form/pre-fill-defaults-on-the-session-form.js @@ -0,0 +1,10 @@ +// Recipe: Pre-fill defaults on the session form +// Surface: Exploratory Session Form +// Pre-fill the title, a 60 minute timebox, the charter in Description, and a custom field when the form opens. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFormLoad('exploratory_session_form', function () { + TM.setFieldValue('title', '[ES] Ad-hoc exploratory session'); // standard field + TM.setFieldValue('timebox', '60'); // 60 minutes + TM.setFieldValue('description', 'Charter set by UI Script'); // standard RTE field + TM.setFieldValue('UIS String', 'Auto-filled by policy'); // custom field +}); diff --git a/ui-scripts/exploratory-session-form/require-a-dropdown-when-a-checkbox-is-on.js b/ui-scripts/exploratory-session-form/require-a-dropdown-when-a-checkbox-is-on.js new file mode 100644 index 0000000..9a11c87 --- /dev/null +++ b/ui-scripts/exploratory-session-form/require-a-dropdown-when-a-checkbox-is-on.js @@ -0,0 +1,7 @@ +// Recipe: Require a dropdown when a checkbox is on +// Surface: Exploratory Session Form +// Toggle the boolean field on and the dropdown becomes required. Toggle it off and the requirement clears. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFieldChange('UIS Boolean', function (val) { + TM.setFieldRequired('UIS Dropdown', (val === true || val === 'true')); +}); diff --git a/ui-scripts/exploratory-session-form/require-a-dropdown-when-a-text-field-says-automatable.js b/ui-scripts/exploratory-session-form/require-a-dropdown-when-a-text-field-says-automatable.js new file mode 100644 index 0000000..3cb4779 --- /dev/null +++ b/ui-scripts/exploratory-session-form/require-a-dropdown-when-a-text-field-says-automatable.js @@ -0,0 +1,8 @@ +// Recipe: Require a dropdown when a text field says Automatable +// Surface: Exploratory Session Form +// A text field is a reliable trigger because its value is exactly what the tester types. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFieldChange('UIS String', function (val) { + var v = (val && val.value !== undefined) ? val.value : (val || ''); + TM.setFieldRequired('UIS Dropdown', String(v).toLowerCase().indexOf('automatable') >= 0); +}); diff --git a/ui-scripts/exploratory-session-form/require-a-linked-requirement-on-a-session.js b/ui-scripts/exploratory-session-form/require-a-linked-requirement-on-a-session.js new file mode 100644 index 0000000..f1f4528 --- /dev/null +++ b/ui-scripts/exploratory-session-form/require-a-linked-requirement-on-a-session.js @@ -0,0 +1,9 @@ +// Recipe: Require a linked requirement on a session +// Surface: Exploratory Session Form +// Block the save until at least one requirement is linked. The same shape works with testPlan to require a linked test plan. + +TM.onBeforeSave('exploratory_session_form', function (form) { + if (!form.requirements || form.requirements.length === 0) { + return TM.blockSave('Link at least one requirement before saving', 'requirements'); // inline error on the Requirements field + } +}); diff --git a/ui-scripts/exploratory-session-form/show-or-hide-fields-on-the-session-form.js b/ui-scripts/exploratory-session-form/show-or-hide-fields-on-the-session-form.js new file mode 100644 index 0000000..18e531e --- /dev/null +++ b/ui-scripts/exploratory-session-form/show-or-hide-fields-on-the-session-form.js @@ -0,0 +1,11 @@ +// Recipe: Show or hide fields on the session form +// Surface: Exploratory Session Form +// Hide one custom field when the form opens, and hide another while a boolean field is on. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +// Static hide on load: +TM.onFormLoad('exploratory_session_form', function () { TM.setFieldVisible('UIS Url', false); }); + +// Conditional hide (UIS MultiSel hidden while UIS Boolean is ON): +TM.onFieldChange('UIS Boolean', function (val) { + TM.setFieldVisible('UIS MultiSel', !(val === true || val === 'true')); +}); diff --git a/ui-scripts/test-case-form/add-a-form-button-that-opens-a-dialog.js b/ui-scripts/test-case-form/add-a-form-button-that-opens-a-dialog.js new file mode 100644 index 0000000..97669db --- /dev/null +++ b/ui-scripts/test-case-form/add-a-form-button-that-opens-a-dialog.js @@ -0,0 +1,17 @@ +// Recipe: Add a form button that opens a dialog +// Surface: Test Case Form +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.addButton('test_case_form', { label: 'Helper' }); + +TM.onButtonClick('Helper', function () { + TM.openDialog({ + title: 'Helper', + content: TM.ui.Stack({ gap: 4 }, [ + TM.ui.Text({}, 'Hello from a UI Script'), + TM.ui.Row({ justify: 'end' }, [ + TM.ui.Button({ variant: 'primary', action: 'cancel' }, 'OK') + ]) + ]) + }); +}); diff --git a/ui-scripts/test-case-form/add-a-form-button-that-opens-a-link.js b/ui-scripts/test-case-form/add-a-form-button-that-opens-a-link.js new file mode 100644 index 0000000..546b598 --- /dev/null +++ b/ui-scripts/test-case-form/add-a-form-button-that-opens-a-link.js @@ -0,0 +1,10 @@ +// Recipe: Add a form button that opens a link +// Surface: Test Case Form +// TM.openUrl opens http and https addresses only and ignores every other scheme. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.addButton('test_case_form', { label: 'Open Docs' }); + +TM.onButtonClick('Open Docs', function () { + TM.openUrl('https://www.browserstack.com/docs/test-management'); +}); diff --git a/ui-scripts/test-case-form/build-a-dialog-with-your-own-buttons.js b/ui-scripts/test-case-form/build-a-dialog-with-your-own-buttons.js new file mode 100644 index 0000000..fdc0645 --- /dev/null +++ b/ui-scripts/test-case-form/build-a-dialog-with-your-own-buttons.js @@ -0,0 +1,23 @@ +// Recipe: Build a dialog with your own buttons +// Surface: Test Case Form +// A dialog built from a content tree renders no footer, so you supply the buttons. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.addButton('test_case_form', { label: 'Open Dialog' }); + +TM.onButtonClick('Open Dialog', function () { + TM.openDialog({ + title: 'Dialog', + content: TM.ui.Stack({ gap: 8 }, [ + TM.ui.Text({}, 'Pick an action'), + TM.ui.TextField({ name: 'note', label: 'Note' }), + TM.ui.Button({ action: 'stayOpen' }, 'Stays open'), + TM.ui.Button({ variant: 'primary', action: 'done' }, 'Run and close') + ]) + }); +}); + +TM.onAction('done', function (data) { + // Do your work with data.note, then close. + TM.closeDialog(); +}); diff --git a/ui-scripts/test-case-form/enforce-a-title-naming-convention.js b/ui-scripts/test-case-form/enforce-a-title-naming-convention.js new file mode 100644 index 0000000..ffe2a62 --- /dev/null +++ b/ui-scripts/test-case-form/enforce-a-title-naming-convention.js @@ -0,0 +1,10 @@ +// Recipe: Enforce a title naming convention +// Surface: Test Case Form +// The error renders under the Title field. Change the regular expression to match your own convention. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_case_form', function (form) { + if (!/^TC-/.test(form.title || '')) { + return TM.blockSave("Title must start with 'TC-'.", 'Title'); + } +}); diff --git a/ui-scripts/test-case-form/forbid-an-invalid-automation-status-and-type-combination.js b/ui-scripts/test-case-form/forbid-an-invalid-automation-status-and-type-combination.js new file mode 100644 index 0000000..11626aa --- /dev/null +++ b/ui-scripts/test-case-form/forbid-an-invalid-automation-status-and-type-combination.js @@ -0,0 +1,12 @@ +// Recipe: Forbid an invalid Automation Status and Type combination +// Surface: Test Case Form +// Both fields are system fields, so both comparisons use internal_name. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_case_form', function (form) { + var automation = form.automationStatus && form.automationStatus.internal_name; + var caseType = form.caseType && form.caseType.internal_name; + if (automation === 'automated' && caseType === 'other') { + return TM.blockSave('An automated test case cannot have the type Other.'); + } +}); diff --git a/ui-scripts/test-case-form/hide-a-field.js b/ui-scripts/test-case-form/hide-a-field.js new file mode 100644 index 0000000..2f38b15 --- /dev/null +++ b/ui-scripts/test-case-form/hide-a-field.js @@ -0,0 +1,8 @@ +// Recipe: Hide a field +// Surface: Test Case Form +// TM.setFieldVisible(name, false) removes the field from the form entirely. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_case_form', function () { + TM.setFieldVisible('Target Region', false); +}); diff --git a/ui-scripts/test-case-form/hide-or-lock-a-built-in-field.js b/ui-scripts/test-case-form/hide-or-lock-a-built-in-field.js new file mode 100644 index 0000000..59aba12 --- /dev/null +++ b/ui-scripts/test-case-form/hide-or-lock-a-built-in-field.js @@ -0,0 +1,10 @@ +// Recipe: Hide or lock a built-in field +// Surface: Test Case Form +// Built-in fields are addressed by their form label. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_case_form', function () { + TM.setFieldReadOnly('Tags', true); + TM.setFieldVisible('Preconditions', false); + TM.setFieldVisible('Steps', false); +}); diff --git a/ui-scripts/test-case-form/lock-a-field-against-editing.js b/ui-scripts/test-case-form/lock-a-field-against-editing.js new file mode 100644 index 0000000..14997ef --- /dev/null +++ b/ui-scripts/test-case-form/lock-a-field-against-editing.js @@ -0,0 +1,7 @@ +// Recipe: Lock a field against editing +// Surface: Test Case Form +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_case_form', function () { + TM.setFieldReadOnly('Target Region', true); +}); diff --git a/ui-scripts/test-case-form/make-an-optional-field-required.js b/ui-scripts/test-case-form/make-an-optional-field-required.js new file mode 100644 index 0000000..48fe131 --- /dev/null +++ b/ui-scripts/test-case-form/make-an-optional-field-required.js @@ -0,0 +1,8 @@ +// Recipe: Make an optional field required +// Surface: Test Case Form +// The field gains a required marker and blocks the save while it is empty. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_case_form', function () { + TM.setFieldRequired('Target Region', true); +}); diff --git a/ui-scripts/test-case-form/pre-fill-a-custom-dropdown.js b/ui-scripts/test-case-form/pre-fill-a-custom-dropdown.js new file mode 100644 index 0000000..8abcb41 --- /dev/null +++ b/ui-scripts/test-case-form/pre-fill-a-custom-dropdown.js @@ -0,0 +1,8 @@ +// Recipe: Pre-fill a custom dropdown +// Surface: Test Case Form +// On a custom dropdown, TM.setFieldValue takes an option object shaped as { value, label }. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_case_form', function () { + TM.setFieldValue('Target Region', { value: 'EMEA', label: 'EMEA' }); +}); diff --git a/ui-scripts/test-case-form/require-a-field-when-automation-status-becomes-automated.js b/ui-scripts/test-case-form/require-a-field-when-automation-status-becomes-automated.js new file mode 100644 index 0000000..e009d57 --- /dev/null +++ b/ui-scripts/test-case-form/require-a-field-when-automation-status-becomes-automated.js @@ -0,0 +1,9 @@ +// Recipe: Require a field when Automation Status becomes Automated +// Surface: Test Case Form +// When the status becomes Automated, Automation Link gains a required marker. When it changes to anything else, the requirement clears. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFieldChange('automationStatus', function (value) { + var status = value && (value.internal_name || value); + TM.setFieldRequired('Automation Link', status === 'automated'); +}); diff --git a/ui-scripts/test-case-form/require-a-linked-requirement.js b/ui-scripts/test-case-form/require-a-linked-requirement.js new file mode 100644 index 0000000..9442de8 --- /dev/null +++ b/ui-scripts/test-case-form/require-a-linked-requirement.js @@ -0,0 +1,9 @@ +// Recipe: Require a linked requirement +// Surface: Test Case Form +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_case_form', function (form) { + if (!form.requirements || form.requirements.length === 0) { + return TM.blockSave('Link at least one requirement before saving.', 'Requirements'); + } +}); diff --git a/ui-scripts/test-case-form/require-a-tag-on-critical-test-cases.js b/ui-scripts/test-case-form/require-a-tag-on-critical-test-cases.js new file mode 100644 index 0000000..e1b422c --- /dev/null +++ b/ui-scripts/test-case-form/require-a-tag-on-critical-test-cases.js @@ -0,0 +1,11 @@ +// Recipe: Require a tag on Critical test cases +// Surface: Test Case Form +// Priority is a system field, so the rule compares internal_name. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_case_form', function (form) { + if (form.priority && form.priority.internal_name === 'critical' && + (form.tags || []).length === 0) { + return TM.blockSave('Critical test cases must have at least one tag.', 'Tags'); + } +}); diff --git a/ui-scripts/test-case-form/require-at-least-one-non-empty-step.js b/ui-scripts/test-case-form/require-at-least-one-non-empty-step.js new file mode 100644 index 0000000..699595a --- /dev/null +++ b/ui-scripts/test-case-form/require-at-least-one-non-empty-step.js @@ -0,0 +1,14 @@ +// Recipe: Require at least one non-empty step +// Surface: Test Case Form +// Each step item exposes its instruction as s.step. Steps cannot show an inline error, so this message appears in the message strip. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_case_form', function (form) { + var items = (form.steps && form.steps.items) || []; + var hasStep = items.some(function (s) { + return s && s.step && String(s.step).trim(); + }); + if (!hasStep) { + return TM.blockSave('At least one non-empty step is required.'); + } +}); diff --git a/ui-scripts/test-case-form/require-one-custom-field-based-on-another.js b/ui-scripts/test-case-form/require-one-custom-field-based-on-another.js new file mode 100644 index 0000000..502e7fc --- /dev/null +++ b/ui-scripts/test-case-form/require-one-custom-field-based-on-another.js @@ -0,0 +1,16 @@ +// Recipe: Require one custom field based on another +// Surface: Test Case Form +// A custom dropdown arrives as { value, label } and carries no internal_name, so the rule compares label. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_case_form', function (form) { + var region = form.fields && form.fields['Target Region']; + var label = region && (region.label || region); + var link = form.fields && form.fields['Automation Link']; + if (label === 'EMEA' && !String(link || '').trim()) { + return TM.blockSave( + "Automation Link is required when Target Region is 'EMEA'.", + 'Automation Link' + ); + } +}); diff --git a/ui-scripts/test-case-form/require-preconditions.js b/ui-scripts/test-case-form/require-preconditions.js new file mode 100644 index 0000000..bf6c40d --- /dev/null +++ b/ui-scripts/test-case-form/require-preconditions.js @@ -0,0 +1,11 @@ +// Recipe: Require preconditions +// Surface: Test Case Form +// Preconditions holds rich text, so the rule strips the tags before it tests for emptiness. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_case_form', function (form) { + var text = String(form.preconditions || '').replace(/<[^>]*>/g, '').trim(); + if (!text) { + return TM.blockSave('Preconditions are required.', 'Preconditions'); + } +}); diff --git a/ui-scripts/test-case-form/restrict-the-options-on-a-dropdown.js b/ui-scripts/test-case-form/restrict-the-options-on-a-dropdown.js new file mode 100644 index 0000000..628396d --- /dev/null +++ b/ui-scripts/test-case-form/restrict-the-options-on-a-dropdown.js @@ -0,0 +1,8 @@ +// Recipe: Restrict the options on a dropdown +// Surface: Test Case Form +// TM.constrainOptions limits a dropdown, a multi-select, or a nested field to the labels you list. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_case_form', function () { + TM.constrainOptions('Target Region', ['EMEA', 'APAC']); +}); diff --git a/ui-scripts/test-case-form/show-a-field-only-when-priority-is-critical.js b/ui-scripts/test-case-form/show-a-field-only-when-priority-is-critical.js new file mode 100644 index 0000000..02a3d1e --- /dev/null +++ b/ui-scripts/test-case-form/show-a-field-only-when-priority-is-critical.js @@ -0,0 +1,17 @@ +// Recipe: Show a field only when Priority is Critical +// Surface: Test Case Form +// A conditional rule needs two triggers. onFormLoad applies it to the form as it opens, and onFieldChange reapplies it every time Priority changes. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +function applyRegionVisibility(priority) { + var value = priority && (priority.internal_name || priority); + TM.setFieldVisible('Target Region', value === 'critical'); +} + +TM.onFormLoad('test_case_form', function (form) { + applyRegionVisibility(form.priority); +}); + +TM.onFieldChange('priority', function (value) { + applyRegionVisibility(value); +}); diff --git a/ui-scripts/test-case-results-toolbar/file-a-pre-filled-bug-in-your-tracker.js b/ui-scripts/test-case-results-toolbar/file-a-pre-filled-bug-in-your-tracker.js new file mode 100644 index 0000000..2beddb7 --- /dev/null +++ b/ui-scripts/test-case-results-toolbar/file-a-pre-filled-bug-in-your-tracker.js @@ -0,0 +1,56 @@ +// Recipe: File a pre-filled bug in your tracker +// Surface: Test Case Results Toolbar +// One click opens your tracker's create-issue page with the title, steps, expected result, and actual result already filled in. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +var allowedProjects = []; +var folderMapping = { + "": [{ value: "", label: "" }] +}; + +TM.addButton('test_case_results_toolbar', { label: '1-Click Bug' }); + +TM.onButtonClick('1-Click Bug', function (context) { + if (allowedProjects.indexOf(context.projectId) === -1) return; + + var steps = ''; + var expected = 'N/A'; + if (context.testCaseSteps && context.testCaseSteps.length > 0) { + context.testCaseSteps.forEach(function (s) { + steps += '\n' + s.number + '. ' + (s.content || ''); + }); + var last = context.testCaseSteps[context.testCaseSteps.length - 1]; + expected = (last && last.expected) || 'N/A'; + } + + var description = '**Test Case:** ' + (context.testCaseTitle || 'N/A'); + description += '\n**Preconditions:** ' + (context.testCasePreconditions || 'N/A'); + description += '\n**Repro Steps:** ' + (steps || 'N/A'); + description += '\n**Expected:** ' + expected; + description += '\n**Actual:** ' + (context.latestResultComment || 'N/A'); + + var folders = folderMapping[String(context.projectId)] || + [{ value: 'default', label: 'Default' }]; + + TM.openDialog({ + title: 'File a bug', + size: 'large', + content: TM.ui.Stack({ gap: 4 }, [ + TM.ui.TextField({ name: 'title', label: 'Title', required: true }), + TM.ui.TextArea({ name: 'description', label: 'Description', rows: 12, defaultValue: description }), + TM.ui.Select({ name: 'folder', label: 'Queue', options: folders }), + TM.ui.Divider(), + TM.ui.Row({ justify: 'end', gap: 2 }, [ + TM.ui.Button({ variant: 'white', action: 'cancel' }, 'Cancel'), + TM.ui.Button({ variant: 'primary', action: 'submit' }, 'Create') + ]) + ]), + onSubmit: function (formData) { + TM.openUrl('/create' + + '?title=' + encodeURIComponent(formData.title || '') + + '&description=' + encodeURIComponent(formData.description || '') + + '&queue=' + encodeURIComponent(formData.folder || '')); + TM.closeDialog(); + } + }); +}); diff --git a/ui-scripts/test-case-results-toolbar/open-a-runbook-from-the-toolbar.js b/ui-scripts/test-case-results-toolbar/open-a-runbook-from-the-toolbar.js new file mode 100644 index 0000000..e36babf --- /dev/null +++ b/ui-scripts/test-case-results-toolbar/open-a-runbook-from-the-toolbar.js @@ -0,0 +1,10 @@ +// Recipe: Open a runbook from the toolbar +// Surface: Test Case Results Toolbar +// A toolbar button can open any page you keep outside Test Management. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.addButton('test_case_results_toolbar', { label: 'Team Runbook' }); + +TM.onButtonClick('Team Runbook', function () { + TM.openUrl('/qa-runbook'); +}); diff --git a/ui-scripts/test-case-results-toolbar/run-your-own-handler-from-a-dialog-button.js b/ui-scripts/test-case-results-toolbar/run-your-own-handler-from-a-dialog-button.js new file mode 100644 index 0000000..78ac58b --- /dev/null +++ b/ui-scripts/test-case-results-toolbar/run-your-own-handler-from-a-dialog-button.js @@ -0,0 +1,22 @@ +// Recipe: Run your own handler from a dialog button +// Surface: Test Case Results Toolbar +// Use this pattern whenever a dialog has to do work first and close afterwards. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.addButton('test_case_results_toolbar', { label: 'Triage Helper' }); + +TM.onButtonClick('Triage Helper', function () { + TM.openDialog({ + title: 'Triage', + content: TM.ui.Stack({ gap: 8 }, [ + TM.ui.Text({}, 'Add a triage note'), + TM.ui.TextField({ name: 'note', label: 'Note' }), + TM.ui.Button({ action: 'saveNote' }, 'Save note and close') + ]) + }); +}); + +TM.onAction('saveNote', function (data) { + // data.note holds the TextField value. Do your work, then close. + TM.closeDialog(); +}); diff --git a/ui-scripts/test-plan-form/combine-rules-on-the-test-plan-form.js b/ui-scripts/test-plan-form/combine-rules-on-the-test-plan-form.js new file mode 100644 index 0000000..1eccbc9 --- /dev/null +++ b/ui-scripts/test-plan-form/combine-rules-on-the-test-plan-form.js @@ -0,0 +1,34 @@ +// Recipe: Combine rules on the test plan form +// Surface: Test Plan Form +// Require, hide, lock, and pre-fill custom fields on load, drive one field from another, and enforce the [TP] prefix in one script. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +// onFormLoad — fires once when the modal opens +TM.onFormLoad('test_plan_form', function (form) { + TM.setFieldRequired('UIS String', true); // String CF shows the required * + TM.setFieldVisible('UIS Url', false); // Url CF removed from the form + TM.setFieldReadOnly('UIS Integer', true); // Integer CF input disabled + TM.setFieldValue('UIS String', 'Prefilled'); // prefill (controlled fields render; RTE saves only) +}); + +// onFieldChange — conditional / bidirectional +// Toggle "UIS Boolean" ON -> Dropdown required, MultiSel hidden, Date read-only. OFF -> revert. +TM.onFieldChange('UIS Boolean', function (val) { + var on = (val === true || val === 'true'); + TM.setFieldRequired('UIS Dropdown', on); + TM.setFieldVisible('UIS MultiSel', !on); + TM.setFieldReadOnly('UIS Date', on); +}); + +// onFieldChange on a standard field (title) driving a custom field +// Type "URGENT" anywhere in the Title -> UIS User becomes required. +TM.onFieldChange('title', function (val) { + var v = (val && val.value !== undefined) ? val.value : (val || ''); + TM.setFieldRequired('UIS User', String(v).indexOf('URGENT') >= 0); +}); + +// onBeforeSave — block with a message (note the RETURN) +TM.onBeforeSave('test_plan_form', function (form) { + if (!/^\[TP\]/.test(form.title || '')) { + return TM.blockSave('Title must start with [TP]', 'title'); + } +}); diff --git a/ui-scripts/test-plan-form/enforce-a-test-plan-name-convention.js b/ui-scripts/test-plan-form/enforce-a-test-plan-name-convention.js new file mode 100644 index 0000000..902c88b --- /dev/null +++ b/ui-scripts/test-plan-form/enforce-a-test-plan-name-convention.js @@ -0,0 +1,9 @@ +// Recipe: Enforce a test plan name convention +// Surface: Test Plan Form +// Block the save unless the test plan title starts with [TP]. The error renders inline under the title. + +TM.onBeforeSave('test_plan_form', function (form) { + if (!/^\[TP\]/.test(form.title || '')) { + return TM.blockSave('Title must start with [TP]', 'title'); + } +}); diff --git a/ui-scripts/test-plan-form/keep-the-end-date-after-the-start-date.js b/ui-scripts/test-plan-form/keep-the-end-date-after-the-start-date.js new file mode 100644 index 0000000..fdd3c94 --- /dev/null +++ b/ui-scripts/test-plan-form/keep-the-end-date-after-the-start-date.js @@ -0,0 +1,20 @@ +// Recipe: Keep the end date after the start date +// Surface: Test Plan Form +// A test plan with an end date before its start date breaks scheduling, so this rule makes the dates depend on each other. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_plan_form', function () { + TM.setFieldRequired('startDate', true); + TM.setFieldReadOnly('endDate', true); +}); + +TM.onFieldChange('startDate', function (value) { + TM.setFieldReadOnly('endDate', !value); + TM.setFieldRequired('endDate', !!value); +}); + +TM.onBeforeSave('test_plan_form', function (form) { + if (form.startDate && form.endDate && form.endDate < form.startDate) { + return TM.blockSave('End date cannot be before the start date.', 'endDate'); + } +}); diff --git a/ui-scripts/test-plan-form/lock-a-field-on-the-test-plan-form.js b/ui-scripts/test-plan-form/lock-a-field-on-the-test-plan-form.js new file mode 100644 index 0000000..6662fa2 --- /dev/null +++ b/ui-scripts/test-plan-form/lock-a-field-on-the-test-plan-form.js @@ -0,0 +1,5 @@ +// Recipe: Lock a field on the test plan form +// Surface: Test Plan Form +// Disable a custom field when the form opens. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFormLoad('test_plan_form', function () { TM.setFieldReadOnly('UIS Integer', true); }); diff --git a/ui-scripts/test-plan-form/pre-fill-defaults-on-the-test-plan-form.js b/ui-scripts/test-plan-form/pre-fill-defaults-on-the-test-plan-form.js new file mode 100644 index 0000000..1647939 --- /dev/null +++ b/ui-scripts/test-plan-form/pre-fill-defaults-on-the-test-plan-form.js @@ -0,0 +1,9 @@ +// Recipe: Pre-fill defaults on the test plan form +// Surface: Test Plan Form +// Pre-fill two custom fields and the built-in Description when the form opens. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFormLoad('test_plan_form', function () { + TM.setFieldValue('UIS String', 'Auto-filled by policy'); + TM.setFieldValue('UIS Integer', '7'); + TM.setFieldValue('description', 'Default description set by UI Script'); // standard field +}); diff --git a/ui-scripts/test-plan-form/require-a-dropdown-when-a-checkbox-is-on.js b/ui-scripts/test-plan-form/require-a-dropdown-when-a-checkbox-is-on.js new file mode 100644 index 0000000..d541b68 --- /dev/null +++ b/ui-scripts/test-plan-form/require-a-dropdown-when-a-checkbox-is-on.js @@ -0,0 +1,7 @@ +// Recipe: Require a dropdown when a checkbox is on +// Surface: Test Plan Form +// Toggle the boolean field on and the dropdown becomes required. Toggle it off and the requirement clears. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFieldChange('UIS Boolean', function (val) { + TM.setFieldRequired('UIS Dropdown', (val === true || val === 'true')); +}); diff --git a/ui-scripts/test-plan-form/require-a-dropdown-when-a-text-field-says-automatable.js b/ui-scripts/test-plan-form/require-a-dropdown-when-a-text-field-says-automatable.js new file mode 100644 index 0000000..2b284ca --- /dev/null +++ b/ui-scripts/test-plan-form/require-a-dropdown-when-a-text-field-says-automatable.js @@ -0,0 +1,8 @@ +// Recipe: Require a dropdown when a text field says Automatable +// Surface: Test Plan Form +// A text field is a reliable trigger because its value is exactly what the tester types. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +TM.onFieldChange('UIS String', function (val) { + var v = (val && val.value !== undefined) ? val.value : (val || ''); + TM.setFieldRequired('UIS Dropdown', String(v).toLowerCase().indexOf('automatable') >= 0); +}); diff --git a/ui-scripts/test-plan-form/require-a-linked-requirement-on-a-test-plan.js b/ui-scripts/test-plan-form/require-a-linked-requirement-on-a-test-plan.js new file mode 100644 index 0000000..b602948 --- /dev/null +++ b/ui-scripts/test-plan-form/require-a-linked-requirement-on-a-test-plan.js @@ -0,0 +1,9 @@ +// Recipe: Require a linked requirement on a test plan +// Surface: Test Plan Form +// Block the save until at least one requirement is linked. The error renders inline under the Requirements field. + +TM.onBeforeSave('test_plan_form', function (form) { + if (!form.requirements || form.requirements.length === 0) { + return TM.blockSave('Link at least one requirement before saving', 'requirements'); // inline error on the Requirements field + } +}); diff --git a/ui-scripts/test-plan-form/show-or-hide-fields-on-the-test-plan-form.js b/ui-scripts/test-plan-form/show-or-hide-fields-on-the-test-plan-form.js new file mode 100644 index 0000000..5093f5e --- /dev/null +++ b/ui-scripts/test-plan-form/show-or-hide-fields-on-the-test-plan-form.js @@ -0,0 +1,11 @@ +// Recipe: Show or hide fields on the test plan form +// Surface: Test Plan Form +// Hide one custom field when the form opens, and hide another while a boolean field is on. Custom field names such as UIS String and UIS Boolean come from a sample project. Swap in the names your own project uses. + +// Static hide on load: +TM.onFormLoad('test_plan_form', function () { TM.setFieldVisible('UIS Url', false); }); + +// Conditional hide (UIS MultiSel hidden while UIS Boolean is ON): +TM.onFieldChange('UIS Boolean', function (val) { + TM.setFieldVisible('UIS MultiSel', !(val === true || val === 'true')); +}); diff --git a/ui-scripts/test-run-form/combine-rules-on-the-run-form.js b/ui-scripts/test-run-form/combine-rules-on-the-run-form.js new file mode 100644 index 0000000..ef07702 --- /dev/null +++ b/ui-scripts/test-run-form/combine-rules-on-the-run-form.js @@ -0,0 +1,28 @@ +// Recipe: Combine rules on the run form +// Surface: Test Run Form +// Pre-fill the run name, require Description, lock Assign Run, hide State, react to URGENT in the title, and enforce the [TR] prefix in one script. + +// onFormLoad — fires once when the form opens +TM.onFormLoad('test_run_form', function (form) { + TM.setFieldValue('title', '[TR] Prefilled by script'); // prefill the run name + TM.setFieldRequired('description', true); // Description shows the required * + TM.setFieldReadOnly('owner', true); // Assign Run disabled + TM.setFieldVisible('state', false); // State field removed from the form +}); + +// onFieldChange — reactive / bidirectional +// Type "URGENT" anywhere in the run name -> Tags becomes required AND Configurations hides. +// Remove it -> both revert. +TM.onFieldChange('title', function (val) { + var v = (val && val.value !== undefined) ? val.value : (val || ''); + var urgent = String(v).indexOf('URGENT') >= 0; + TM.setFieldRequired('tags', urgent); + TM.setFieldVisible('configurations', !urgent); +}); + +// onBeforeSave — block with a message (note the RETURN) +TM.onBeforeSave('test_run_form', function (form) { + if (!/^\[TR\]/.test(form.title || '')) { + return TM.blockSave('Run name must start with [TR]', 'title'); + } +}); diff --git a/ui-scripts/test-run-form/enforce-a-run-name-convention.js b/ui-scripts/test-run-form/enforce-a-run-name-convention.js new file mode 100644 index 0000000..f10bb9c --- /dev/null +++ b/ui-scripts/test-run-form/enforce-a-run-name-convention.js @@ -0,0 +1,10 @@ +// Recipe: Enforce a run name convention +// Surface: Test Run Form +// Every test run carries a prefix that your reports can group on. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_run_form', function (form) { + if (!/^\[TR\]/.test(form.title || '')) { + return TM.blockSave('Run name must start with [TR].', 'title'); + } +}); diff --git a/ui-scripts/test-run-form/lock-assign-run-and-configurations.js b/ui-scripts/test-run-form/lock-assign-run-and-configurations.js new file mode 100644 index 0000000..c566015 --- /dev/null +++ b/ui-scripts/test-run-form/lock-assign-run-and-configurations.js @@ -0,0 +1,8 @@ +// Recipe: Lock Assign Run and Configurations +// Surface: Test Run Form +// Disable the Assign Run and Configurations fields when the form opens. + +TM.onFormLoad('test_run_form', function () { + TM.setFieldReadOnly('owner', true); // Assign Run disabled + TM.setFieldReadOnly('configurations', true); // Configurations disabled +}); diff --git a/ui-scripts/test-run-form/pre-fill-defaults-when-a-form-opens.js b/ui-scripts/test-run-form/pre-fill-defaults-when-a-form-opens.js new file mode 100644 index 0000000..73f5557 --- /dev/null +++ b/ui-scripts/test-run-form/pre-fill-defaults-when-a-form-opens.js @@ -0,0 +1,9 @@ +// Recipe: Pre-fill defaults when a form opens +// Surface: Test Run Form +// Pre-filled defaults spare every tester the same first two edits. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFormLoad('test_run_form', function () { + TM.setFieldValue('title', '[TR] Nightly regression'); + TM.setFieldValue('description', 'Created under the release policy.'); +}); diff --git a/ui-scripts/test-run-form/require-a-field-when-the-title-marks-a-run-urgent.js b/ui-scripts/test-run-form/require-a-field-when-the-title-marks-a-run-urgent.js new file mode 100644 index 0000000..14f9d3f --- /dev/null +++ b/ui-scripts/test-run-form/require-a-field-when-the-title-marks-a-run-urgent.js @@ -0,0 +1,11 @@ +// Recipe: Require a field when the title marks a run urgent +// Surface: Test Run Form +// This recipe watches the run name for the word URGENT. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onFieldChange('title', function (value) { + var text = (value && value.value !== undefined) ? value.value : (value || ''); + var urgent = String(text).indexOf('URGENT') >= 0; + TM.setFieldRequired('tags', urgent); + TM.setFieldVisible('configurations', !urgent); +}); diff --git a/ui-scripts/test-run-form/require-a-test-plan-on-every-run.js b/ui-scripts/test-run-form/require-a-test-plan-on-every-run.js new file mode 100644 index 0000000..108fb65 --- /dev/null +++ b/ui-scripts/test-run-form/require-a-test-plan-on-every-run.js @@ -0,0 +1,10 @@ +// Recipe: Require a test plan on every run +// Surface: Test Run Form +// A run that belongs to no test plan is invisible in plan-level reporting, so block it at create time. +// Docs: https://www.browserstack.com/docs/test-management/advanced-features/ui-scripts/ui-script-examples + +TM.onBeforeSave('test_run_form', function (form) { + if (!form.testPlans || form.testPlans.length === 0) { + return TM.blockSave('Link a Test Plan before creating the run.', 'testPlans'); + } +}); diff --git a/ui-scripts/test-run-form/require-a-test-plan-when-the-title-says-automatable.js b/ui-scripts/test-run-form/require-a-test-plan-when-the-title-says-automatable.js new file mode 100644 index 0000000..5067608 --- /dev/null +++ b/ui-scripts/test-run-form/require-a-test-plan-when-the-title-says-automatable.js @@ -0,0 +1,8 @@ +// Recipe: Require a test plan when the title says Automatable +// Surface: Test Run Form +// Type Automatable anywhere in the run name and the Test Plan field becomes required. Remove the word and the requirement clears. + +TM.onFieldChange('title', function (val) { + var v = (val && val.value !== undefined) ? val.value : (val || ''); + TM.setFieldRequired('testPlans', String(v).toLowerCase().indexOf('automatable') >= 0); +}); diff --git a/ui-scripts/test-run-form/set-several-field-behaviors-when-the-run-form-opens.js b/ui-scripts/test-run-form/set-several-field-behaviors-when-the-run-form-opens.js new file mode 100644 index 0000000..fe7fd12 --- /dev/null +++ b/ui-scripts/test-run-form/set-several-field-behaviors-when-the-run-form-opens.js @@ -0,0 +1,15 @@ +// Recipe: Set several field behaviors when the run form opens +// Surface: Test Run Form +// Mix required, read-only, hidden, and pre-filled fields inside one onFormLoad callback. A script-required field left empty blocks the save. + +TM.onFormLoad('test_run_form', function () { + TM.setFieldRequired('description', true); // description required + TM.setFieldRequired('tags', true); // tags required (child field) + TM.setFieldRequired('testPlans', true); // test plan required (child field) + TM.setFieldRequired('runGroup', true); // run group required (if run groups enabled) + TM.setFieldReadOnly('owner', true); // owner read-only + TM.setFieldReadOnly('autoAssign', true); // checkbox disabled + TM.setFieldVisible('state', false); // state hidden + TM.setFieldVisible('configurations', false);// configurations hidden + TM.setFieldValue('title', '[TR] Prefilled');// name prefilled +}); diff --git a/ui-scripts/test-run-form/show-or-hide-fields-on-the-run-form.js b/ui-scripts/test-run-form/show-or-hide-fields-on-the-run-form.js new file mode 100644 index 0000000..d417b45 --- /dev/null +++ b/ui-scripts/test-run-form/show-or-hide-fields-on-the-run-form.js @@ -0,0 +1,15 @@ +// Recipe: Show or hide fields on the run form +// Surface: Test Run Form +// Hide State and the Auto-assign checkbox when the form opens, and hide Configurations while the run name contains URGENT. + +// Static hide on load: +TM.onFormLoad('test_run_form', function () { + TM.setFieldVisible('state', false); // hide State + TM.setFieldVisible('autoAssign', false); // hide the Auto-assign checkbox +}); + +// Conditional hide (Configurations hidden while the name contains "URGENT"): +TM.onFieldChange('title', function (val) { + var v = (val && val.value !== undefined) ? val.value : (val || ''); + TM.setFieldVisible('configurations', String(v).indexOf('URGENT') < 0); +}); From 96e5c66b6abc6514bac5a37d1ea5c38a326f10ef Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 11 Sep 2026 11:58:56 +0530 Subject: [PATCH 2/2] Fix CodeQL: use DOMParser instead of regex to strip HTML in UI scripts Regex-based tag stripping leaves unterminated tags such as '