diff --git a/AGENTS.md b/AGENTS.md
index 43d8381..1aa98fd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -56,6 +56,8 @@ HTMLReport/preview.html ── reads session ── renders stats, charts, filte
| `manifest.json` | MV3 manifest. Permissions: `storage`, `tabs`, `activeTab`, `downloads`, `scripting`, `notifications` |
| `background.js` | Service worker (`type: module`). Owns the `Session`, routes messages, saves to storage, triggers downloads, shows notifications |
| `popup.html`, `js/popup.js` | Popup UI: annotation forms, counters, export/import/clear, open report |
+| `tutorial.html`, `js/tutorial.js`, `css/tutorial.css` | Interactive tutorial (Driver.js tour). Opened by the popup on first use and by the **?** help button. See [Tutorial](#tutorial) |
+| `lib/driverjs/` | Vendored Driver.js (pinned version in its `README.md`), loaded locally — never from a CDN |
| `js/content_script.js` | Crop-area selection on the page; hosts the annotation editor iframe |
| `js/annotation_editor.*` | Screenshot editor (arrow, rectangle, text, blur, undo). Talks to the content script via `postMessage` (`initAnnotationEditor`, `annotationComplete`, `annotationCancelled`) |
| `src/Session.js` | Session model: browser info, start time, `annotations[]` |
@@ -94,6 +96,20 @@ Content script also sends `selectionCancelled` (not handled by background).
- **Download Report** — single self-contained HTML, screenshots inlined as base64, must work offline.
- **Download Images** — ZIP (JSZip) of screenshots named by type + timestamp, plus `README.txt`. Only offered in `preview.html`; deliberately omitted from downloaded reports to avoid Windows security warnings.
+## Tutorial
+
+`tutorial.html` is a guided tour of every user-facing feature: types of annotation, add with / without screenshot, crop with description and blur, report, export / import, and the **?** help button. It is shown automatically the first time the popup opens (`tutorialSeen` flag in the popup's `localStorage`) and reopened from the **?** button in `popup.html`.
+
+**Keep the tutorial in sync.** Whenever you add a user-facing feature or change an existing one (popup buttons or shortcuts, crop / editor tools, report, export / import, session behaviour), update the tutorial in the same change:
+
+1. `js/tutorial.js` — add, edit or remove the step. Steps target `[data-tour="..."]` elements; step text is English.
+2. `tutorial.html` — the popup mock mirrors the markup of `popup.html` (same classes and ids so `css/popUp.css` styles it); mirror UI changes there. Editor and report scenes are screenshots with transparent `.hotspot` boxes (percent positions) that Driver.js highlights.
+3. `images/tutorial/editor.png`, `images/tutorial/report.png` — regenerate when the editor or report UI changes, and re-check the hotspot positions. Use fake data only: no real URLs, names or customer content in screenshots.
+4. `test/e2e/tutorial.spec.js` — the tour test walks every step and requires each one to highlight an existing element, so a removed or renamed `data-tour` target fails it.
+5. If the change is user-visible, also refresh the README screenshots in `screenshots/` and the feature list.
+
+Rules for the tutorial code: no CDN and no remote code (the page makes no external requests; a test enforces it); keep the last step pointing at the help button; the mock is inert, so never load `js/popup.js` in `tutorial.html`.
+
## Testing
### Unit (Jest)
@@ -104,7 +120,8 @@ Content script also sends `selectionCancelled` (not handled by background).
### E2E (Playwright)
-- Specs in `test/e2e/*.spec.js`: `smoke`, `basic-functionality`, `reports-export`, `crop-screenshot`, `annotation-editor`. Shared setup in `test/e2e/helpers/extension-helper.js`; fixture pages in `test/e2e/test-pages/` (served at `http://localhost:8000/test/e2e/test-pages`).
+- Specs in `test/e2e/*.spec.js`: `smoke`, `basic-functionality`, `reports-export`, `crop-screenshot`, `annotation-editor`, `tutorial`. Shared setup in `test/e2e/helpers/extension-helper.js`; fixture pages in `test/e2e/test-pages/` (served at `http://localhost:8000/test/e2e/test-pages`).
+- `openExtensionPopup` marks the tutorial as seen (sets `tutorialSeen` in the extension's `localStorage`) so the first-run tutorial tab does not open during specs. Pass `{ firstRun: true }` to test the first-run behaviour.
- Runs one worker, not parallel (extensions require a persistent Chromium context). Retries 2× on CI.
- `npm run` scripts list spec files explicitly — **a new spec must be added to `test:e2e:all` and `test:e2e:ci` in `package.json`** or CI won't run it.
- Prefer role / label / test-id selectors over CSS paths; use Playwright's auto-waiting assertions, never fixed sleeps.
@@ -139,8 +156,7 @@ If your agent supports these, use them:
## Gotchas
-- `genetareZip.ps1` (filename typo is real) packages an **allowlist** of runtime files (`manifest.json`, `background.js`, `popup.html`, `css`, `HTMLReport`, `icons`, `images`, `js`, `lib`, `src`). A new top-level runtime file or folder must be added to `$include`, or it will be missing from the store package.
-- `README.md` still claims `ExportSessionCSV.test.js` is broken — it now passes.
+- `genetareZip.ps1` (filename typo is real) packages an **allowlist** of runtime files (`manifest.json`, `background.js`, `popup.html`, `tutorial.html`, `css`, `HTMLReport`, `icons`, `images`, `js`, `lib`, `src`). A new top-level runtime file or folder must be added to `$include`, or it will be missing from the store package.
## Definition of done
@@ -148,4 +164,5 @@ If your agent supports these, use them:
2. Relevant E2E specs pass (`npm run test:e2e:all` for UI / message-flow changes).
3. New `chrome.*` calls are mocked in `jest.setup.js`; new specs are wired into `package.json`.
4. Extension reloaded and the change checked manually in Chrome when it affects UI.
-5. This file updated if architecture, commands or conventions changed.
+5. The tutorial (`js/tutorial.js`, `tutorial.html`, images) and README features / screenshots updated if the change is user-visible.
+6. This file updated if architecture, commands or conventions changed.
diff --git a/README.md b/README.md
index 55337b5..6ad32fe 100644
--- a/README.md
+++ b/README.md
@@ -1,62 +1,76 @@
# A Chrome Extension for Exploratory Testing
-A Chrome extension designed for making web exploratory testing easier
+A Chrome extension designed for making web exploratory testing easier. Record what you find while you explore a web page, with the URL and a screenshot attached, and review the whole session in a report.
-**Features**
+Install from [Chrome Store](https://chrome.google.com/webstore/detail/exploratory-testing-chrom/khigmghadjljgjpamimgjjmpmlbgmekj)
- - Report bugs, ideas, notes and questions easily
- - Take screenshots during the session. Keep focused
- - URL will be tracked automatically
- - See session results in a report
- - Export and import sessions
- - Export session to JSON, CSV or HTML
+## Features
+- **Four annotation types**: Bugs, Notes, Questions and Ideas, each with a counter in the popup.
+- **Screenshots without changing context**: add an annotation with no screenshot, with a screenshot of the visible tab, or with a cropped area of the page.
+- **Annotation editor for cropped screenshots**: arrow, rectangle, text and **blur** (to hide sensitive data), with undo and keyboard shortcuts.
+- **Automatic tracking**: the URL, time and browser information are saved with every annotation.
+- **Session report**: statistics per type, distribution chart, filter by type, annotations table and delete.
+- **Download the report** as a standalone HTML file that works offline, or all screenshots as a ZIP.
+- **Export and import sessions**: export to JSON or CSV, and import a JSON session to continue it later.
+- **Interactive tutorial**: a guided tour that opens the first time you use the extension and can be reopened from the **?** button.
+- **Keyboard shortcuts** in the description box: `Ctrl + Enter` adds the annotation, `Shift + Enter` adds it with a screenshot.
-Install from [Chrome Store](https://chrome.google.com/webstore/detail/exploratory-testing-chrom/khigmghadjljgjpamimgjjmpmlbgmekj)
+## Screenshots
+**Annotate any thought while you test and take screenshots without changing context**
-**Annotate any thought while you test easily and take screenshots without changing context**
+
-
+**Crop, annotate and blur the screenshot**
+
**View session results in a report**
-
+
+
+**Learn the features with the built-in tutorial**
+
+
+
+## Tutorial
+
+The extension opens an interactive tutorial the first time you open its popup. It covers adding annotations (with and without screenshots), cropping with description and blur, viewing the report, and exporting or importing a session. To see it again, click the **?** button at the top right of the popup.
+
+The tour is built with [Driver.js](https://driverjs.com/), bundled in `lib/driverjs/` (no CDN). The page is `tutorial.html`, with the steps in `js/tutorial.js`.
## Development and Testing
-This section provides instructions for setting up the project locally for development and running tests.
+Plain JavaScript, no bundler and no runtime npm dependencies: the repository root is the extension. [AGENTS.md](./AGENTS.md) has the architecture, conventions and the full list of commands.
### Project Setup
-1. **Prerequisites**: Ensure you have [Node.js](https://nodejs.org/) installed (which includes npm).
-2. **Clone the Repository**:
- ```bash
- git clone https://github.com/your-username/your-repo-name.git
- cd your-repo-name
- ```
- *(Using a common placeholder format for the repo URL. The user can update this if they wish).*
-3. **Install Dependencies**:
- ```bash
- npm install
- ```
- This will install Jest, Babel, and other necessary development dependencies as defined in `package.json`.
+1. **Prerequisites**: [Node.js](https://nodejs.org/) 20 (includes npm).
+2. **Clone the repository**:
+ ```bash
+ git clone https://github.com/morvader/ExploratoryTestingChromeExtension.git
+ cd ExploratoryTestingChromeExtension
+ ```
+3. **Install development dependencies** (Jest, Playwright):
+ ```bash
+ npm install
+ npx playwright install chromium
+ ```
+4. **Load the extension**: open `chrome://extensions`, enable *Developer mode*, choose *Load unpacked* and select the repository folder.
### Running Tests
-The project uses [Jest](https://jestjs.io/) for unit testing.
-
-1. **Execute Tests**:
- ```bash
- npm test
- ```
- Alternatively, you can run Jest directly:
- ```bash
- npx jest
- ```
-2. **Test Results**: Most tests should pass.
- * **Known Issue**: The test suite for `test/spec/ExportSessionCSV.test.js` is currently non-functional due to a syntax error that occurred during the migration to Jest and could not be resolved with available tools. This specific suite will fail or be skipped. All other test suites should pass.
+| Command | What it runs |
+|---------|--------------|
+| `npm test` | Jest unit tests (`test/spec/`) |
+| `npm run test:e2e` | Playwright smoke and basic functionality |
+| `npm run test:e2e:all` | Full Playwright suite (same as CI) |
+| `npm run test:all` | Unit tests plus the full E2E suite |
+
+### Release package
+
+On Windows, `.\genetareZip.ps1` creates `ETExtension-.zip` with the runtime files only, ready to upload to the Chrome Web Store.
---
_PS: I'm not a web designer, so any help with web design or UX will be appreciated._
diff --git a/css/popUp.css b/css/popUp.css
index 9a71962..a3ce129 100644
--- a/css/popUp.css
+++ b/css/popUp.css
@@ -158,6 +158,32 @@ body {
border-bottom: none;
}
+.header-with-help {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-right: 8px;
+}
+
+.help-btn {
+ width: 26px;
+ height: 26px;
+ padding: 0;
+ border: 1.5px solid var(--color-primary);
+ border-radius: 50%;
+ background: var(--color-surface);
+ color: var(--color-primary);
+ font-weight: 700;
+ line-height: 1;
+ cursor: pointer;
+ transition: all var(--transition);
+}
+
+.help-btn:hover {
+ background: var(--color-primary);
+ color: #ffffff;
+}
+
.card-header h5 {
font-size: 1rem;
font-weight: 600;
diff --git a/css/tutorial.css b/css/tutorial.css
new file mode 100644
index 0000000..553dd1d
--- /dev/null
+++ b/css/tutorial.css
@@ -0,0 +1,130 @@
+/* Tutorial page. Loaded after popUp.css, which also styles the popup mock. */
+
+/* popUp.css limits the body to popup width; the tutorial page needs the full window. */
+body.tutorial {
+ min-width: 0;
+ max-width: none;
+ background-color: var(--color-bg);
+}
+
+.tutorial-header {
+ text-align: center;
+ padding: 40px 16px 24px;
+ background: linear-gradient(135deg, var(--color-primary), #8b5cf6);
+ color: #ffffff;
+}
+
+.tutorial-header h1 {
+ font-size: 1.8rem;
+ font-weight: 700;
+ margin-bottom: 8px;
+}
+
+.tutorial-header p {
+ margin-bottom: 16px;
+ opacity: 0.9;
+}
+
+.tutorial-header .btn-primary {
+ background-color: #ffffff;
+ border-color: #ffffff;
+ color: var(--color-primary-dark);
+ border-radius: var(--radius-md);
+ padding: 8px 20px;
+ font-weight: 600;
+}
+
+.tutorial-main {
+ max-width: 1000px;
+ margin: 0 auto;
+ padding: 24px 16px 64px;
+}
+
+.scene {
+ margin-bottom: 48px;
+}
+
+.scene h2 {
+ font-size: 1.3rem;
+ font-weight: 700;
+ margin-bottom: 4px;
+}
+
+.scene-caption {
+ color: var(--color-text-secondary);
+ margin-bottom: 16px;
+}
+
+/* Popup mock: same width as the real popup, inert. */
+.popup-mock {
+ width: 400px;
+ max-width: 100%;
+ margin: 0 auto;
+ padding: 8px;
+ background: #ffffff;
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-md);
+ pointer-events: none;
+ user-select: none;
+}
+
+.popup-mock #resetBtn {
+ margin-right: 0;
+}
+
+.popup-mock .session-buttons {
+ display: inline-flex;
+ gap: 6px;
+}
+
+/* Screenshots with transparent hotspots that Driver.js can highlight. */
+.shot {
+ position: relative;
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-md);
+ overflow: hidden;
+ background: #ffffff;
+}
+
+.shot img {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.hotspot {
+ position: absolute;
+ display: block;
+ pointer-events: none;
+}
+
+/* Driver.js popover tweaks to match the extension look. */
+.driver-popover.et-tour {
+ font-family: var(--font-sans);
+ border-radius: var(--radius-md);
+ max-width: 340px;
+}
+
+.driver-popover.et-tour .driver-popover-title {
+ font-size: 1.05rem;
+ font-weight: 700;
+}
+
+.driver-popover.et-tour .driver-popover-description {
+ line-height: 1.45;
+}
+
+.driver-popover.et-tour .driver-popover-description kbd {
+ background: #f1f5f9;
+ border: 1px solid var(--color-border);
+ border-radius: 4px;
+ color: var(--color-text);
+ padding: 0 4px;
+}
+
+.driver-popover.et-tour .driver-popover-next-btn,
+.driver-popover.et-tour .driver-popover-prev-btn {
+ border-radius: var(--radius-sm);
+}
diff --git a/genetareZip.ps1 b/genetareZip.ps1
index 25c9133..d1e23a6 100644
--- a/genetareZip.ps1
+++ b/genetareZip.ps1
@@ -15,6 +15,7 @@ $include = @(
"manifest.json",
"background.js",
"popup.html",
+ "tutorial.html",
"css",
"HTMLReport",
"icons",
diff --git a/images/tutorial/editor.png b/images/tutorial/editor.png
new file mode 100644
index 0000000..c7d1fd1
Binary files /dev/null and b/images/tutorial/editor.png differ
diff --git a/images/tutorial/report.png b/images/tutorial/report.png
new file mode 100644
index 0000000..2b6b3f3
Binary files /dev/null and b/images/tutorial/report.png differ
diff --git a/js/popup.js b/js/popup.js
index 1b56e6e..f16ec80 100644
--- a/js/popup.js
+++ b/js/popup.js
@@ -9,7 +9,27 @@ window.onload = function () {
})
}
+const TUTORIAL_SEEN_KEY = 'tutorialSeen';
+
+function openTutorial() {
+ chrome.tabs.create({ url: chrome.runtime.getURL('tutorial.html'), active: true });
+}
+
+// Opens the tutorial the first time the popup is shown; the help button reopens it afterwards.
+function tutorialListeners() {
+ $(document).on('click', '#helpBtn', openTutorial);
+
+ try {
+ if (localStorage.getItem(TUTORIAL_SEEN_KEY)) return;
+ localStorage.setItem(TUTORIAL_SEEN_KEY, '1');
+ } catch (e) {
+ return; // storage unavailable: skip the automatic tutorial rather than show it on every open
+ }
+ openTutorial();
+}
+
function initElements() {
+ tutorialListeners();
annotationListeners();
exportListeners();
updateCounters();
diff --git a/js/tutorial.js b/js/tutorial.js
new file mode 100644
index 0000000..acc2eff
--- /dev/null
+++ b/js/tutorial.js
@@ -0,0 +1,211 @@
+// Guided tour for the extension, built with Driver.js (bundled in lib/driverjs, no CDN).
+// Steps target the popup mock and the screenshot hotspots in tutorial.html via [data-tour].
+
+const tourSteps = [
+ {
+ popover: {
+ title: 'Welcome',
+ description:
+ 'Record Bugs, Notes, Questions and Ideas while you explore a web page. ' +
+ 'This tour covers adding annotations, screenshots, cropping, the report, and exporting or importing a session.',
+ },
+ },
+ {
+ element: '[data-tour="types"]',
+ popover: {
+ title: 'Pick an annotation type',
+ description:
+ 'Click Bug, Note, Question or Idea to open its form. ' +
+ 'The badge shows how many annotations of that type the session has. The URL of the current tab is saved automatically.',
+ side: 'right',
+ },
+ },
+ {
+ element: '[data-tour="description"]',
+ popover: {
+ title: 'Describe what you found',
+ description:
+ 'Type a short description. It is required for cropped screenshots, so write it before you click the crop button.',
+ side: 'right',
+ },
+ },
+ {
+ element: '[data-tour="add"]',
+ popover: {
+ title: 'Add without a screenshot',
+ description:
+ 'Saves the annotation with the description and URL only. Shortcut: Ctrl + Enter inside the text box.',
+ side: 'right',
+ },
+ },
+ {
+ element: '[data-tour="screenshot"]',
+ popover: {
+ title: 'Add with a screenshot',
+ description:
+ 'Saves the annotation together with a screenshot of the visible area of the current tab. ' +
+ 'Shortcut: Shift + Enter inside the text box.',
+ side: 'right',
+ },
+ },
+ {
+ element: '[data-tour="crop"]',
+ popover: {
+ title: 'Add with a cropped screenshot',
+ description:
+ 'The popup closes and you drag a rectangle over the part of the page you want to keep. ' +
+ 'The editor opens with that area so you can mark it up before saving.',
+ side: 'right',
+ },
+ },
+ {
+ element: '[data-tour="editor-canvas"]',
+ popover: {
+ title: 'The crop editor',
+ description:
+ 'Your selected area appears here. The description you typed in the popup is saved with the annotation.',
+ side: 'bottom',
+ },
+ },
+ {
+ element: '[data-tour="editor-tools"]',
+ popover: {
+ title: 'Draw on the screenshot',
+ description:
+ 'Choose an arrow, a rectangle or text, then click and drag on the image. ' +
+ 'Shortcuts: A, R, T. Undo with Ctrl + Z.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="editor-blur-tool"]',
+ popover: {
+ title: 'Blur sensitive data',
+ description:
+ 'Select the blur tool (B) and drag over anything that should not be shared, ' +
+ 'such as e-mails, names or tokens.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="editor-blurred"]',
+ popover: {
+ title: 'Blurred area',
+ description: 'This is how a blurred area looks. The blur is baked into the image when you save with annotations.',
+ side: 'bottom',
+ },
+ },
+ {
+ element: '[data-tour="editor-save"]',
+ popover: {
+ title: 'Save or cancel',
+ description:
+ 'Save with Annotations keeps your drawings and blur (Ctrl + Enter). ' +
+ 'Save without Annotations keeps the plain crop. Cancel (Esc) discards it.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="report-btn"]',
+ popover: {
+ title: 'View the report',
+ description: 'Click this button in the popup to open the session report in a new tab.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="report-summary"]',
+ popover: {
+ title: 'Session summary',
+ description: 'Start date, browser, operating system, the number of annotations per type and their distribution.',
+ side: 'bottom',
+ },
+ },
+ {
+ element: '[data-tour="report-filters"]',
+ popover: {
+ title: 'Filter by type',
+ description: 'Show only Bugs, Notes, Ideas or Questions, or switch back to All.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="report-table"]',
+ popover: {
+ title: 'Annotations table',
+ description:
+ 'Every annotation with its type, description, URL, time and screenshot. ' +
+ 'Use the trash icon to delete an annotation.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="report-downloads"]',
+ popover: {
+ title: 'Download the report',
+ description:
+ 'Download Report creates a standalone HTML file that works offline. ' +
+ 'Download Images creates a ZIP with all the screenshots.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="export"]',
+ popover: {
+ title: 'Export the session',
+ description:
+ 'Export to CSV for spreadsheets or to JSON to keep a complete copy of the session.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="import"]',
+ popover: {
+ title: 'Import a session',
+ description:
+ 'Load a JSON file you exported earlier to continue that session. ' +
+ 'Importing replaces the current session, so export first if you want to keep it.',
+ side: 'top',
+ },
+ },
+ {
+ element: '[data-tour="help"]',
+ popover: {
+ title: 'Want to see this again?',
+ description:
+ 'Click the ? button at the top right of the extension popup to open this tutorial at any time.',
+ side: 'left',
+ },
+ },
+];
+
+function createTour() {
+ const { driver } = window.driver.js;
+ return driver({
+ showProgress: true,
+ allowClose: true,
+ smoothScroll: true,
+ stagePadding: 6,
+ stageRadius: 8,
+ overlayOpacity: 0.65,
+ popoverClass: 'et-tour',
+ progressText: '{{current}} of {{total}}',
+ nextBtnText: 'Next',
+ prevBtnText: 'Back',
+ doneBtnText: 'Finish',
+ steps: tourSteps,
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ let tour = createTour();
+
+ const start = () => {
+ tour.destroy();
+ tour = createTour();
+ tour.drive();
+ };
+
+ document.getElementById('startTourBtn').addEventListener('click', start);
+ start();
+});
diff --git a/lib/driverjs/README.md b/lib/driverjs/README.md
new file mode 100644
index 0000000..31025cc
--- /dev/null
+++ b/lib/driverjs/README.md
@@ -0,0 +1,7 @@
+# Driver.js (bundled)
+
+- Library: [Driver.js](https://driverjs.com/) 1.10.2, MIT license (see `license`).
+- Files: `driver.js.iife.js` and `driver.css`, copied unmodified from `dist/` of the `driver.js@1.10.2` npm package.
+- Used by `tutorial.html` / `js/tutorial.js`. It is bundled instead of loaded from a CDN so the extension makes no external requests.
+
+To upgrade, download the new package version, copy the same two files and `license`, update the version above, and run `npm run test:e2e -- test/e2e/tutorial.spec.js`.
diff --git a/lib/driverjs/driver.css b/lib/driverjs/driver.css
new file mode 100644
index 0000000..5e4b3ec
--- /dev/null
+++ b/lib/driverjs/driver.css
@@ -0,0 +1 @@
+.driver-layer{width:auto;max-width:none;height:auto;max-height:none;color:inherit;pointer-events:none;background:0 0;border:0;margin:0;padding:0;position:fixed;inset:0;overflow:visible}.driver-layer::backdrop{display:none}.driver-layer .driver-popover{pointer-events:auto}.driver-popover{all:unset;font-family:var(--driver-popover-font-family,"Helvetica Neue", Inter, ui-sans-serif, "Apple Color Emoji", Helvetica, Arial, sans-serif);box-sizing:border-box;color:#2d2d2d;z-index:1000000000;background-color:#fff;border-radius:5px;min-width:250px;max-width:300px;margin:0;padding:15px;position:fixed;top:0;right:0;box-shadow:0 1px 10px #0006}.driver-popover-title{zoom:1;margin:0;font-size:19px;font-weight:700;line-height:1.5;display:block;position:relative}.driver-popover-close-btn{all:unset;cursor:pointer;color:#d2d2d2;z-index:1;text-align:center;width:32px;height:28px;font-size:18px;font-weight:500;transition:color .2s;position:absolute;top:0;right:0}.driver-popover-close-btn:hover,.driver-popover-close-btn:focus-visible{color:#2d2d2d}.driver-popover-title[style*=block]+.driver-popover-description{margin-top:5px}.driver-popover-description{zoom:1;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.5}.driver-popover-footer{text-align:right;zoom:1;justify-content:space-between;align-items:center;margin-top:15px;display:flex}.driver-popover-progress-text{color:#727272;zoom:1;font-size:13px;font-weight:400}.driver-popover-footer-btn{all:unset;box-sizing:border-box;color:#2d2d2d;cursor:pointer;zoom:1;background-color:#fff;border:1px solid #ccc;border-radius:3px;outline:0;padding:3px 7px;font-size:12px;line-height:1.3;text-decoration:none;display:inline-block}.driver-popover-footer .driver-popover-btn-disabled{opacity:.5;pointer-events:none}.driver-popover-footer-btn:hover,.driver-popover-footer-btn:focus{background-color:#f7f7f7}.driver-popover-navigation-btns{flex-grow:1;justify-content:flex-end;display:flex}.driver-popover-navigation-btns button+button{margin-left:4px}.driver-popover-arrow{content:"";border:5px solid #fff;position:absolute}.driver-popover-arrow-side-over{display:none}.driver-popover-arrow-side-left{border-top-color:#0000;border-bottom-color:#0000;border-right-color:#0000;left:100%}.driver-popover-arrow-side-right{border-top-color:#0000;border-bottom-color:#0000;border-left-color:#0000;right:100%}.driver-popover-arrow-side-top{border-bottom-color:#0000;border-left-color:#0000;border-right-color:#0000;top:100%}.driver-popover-arrow-side-bottom{border-top-color:#0000;border-left-color:#0000;border-right-color:#0000;bottom:100%}.driver-popover-arrow-side-center,.driver-popover-arrow-none{display:none}.driver-active .driver-overlay{pointer-events:none}.driver-active.driver-no-scroll{overflow:hidden}.driver-active *{pointer-events:none}.driver-active .driver-active-element,.driver-active .driver-active-element *,.driver-popover,.driver-popover *{pointer-events:auto}@keyframes animate-fade-in{0%{opacity:0}to{opacity:1}}.driver-fade .driver-overlay{animation:animate-fade-in var(--driver-animation-duration,.4s) ease-in-out}.driver-fade .driver-popover{animation:animate-fade-in var(--driver-animation-duration,.4s)}.driver-active-element-parent-no-scroll{overflow:hidden!important}.driver-no-interaction,.driver-no-interaction *{pointer-events:none!important}
diff --git a/lib/driverjs/driver.js.iife.js b/lib/driverjs/driver.js.iife.js
new file mode 100644
index 0000000..dee4755
--- /dev/null
+++ b/lib/driverjs/driver.js.iife.js
@@ -0,0 +1,2 @@
+this.driver=this.driver||{},this.driver.js=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});let t=[`pointerdown`,`mousedown`,`pointerup`,`mouseup`,`click`],n=new WeakMap;function r(e,r,a){i(e);let o=t=>{let n=t.target;e.contains(n)&&((!a||a(n))&&(t.preventDefault(),t.stopPropagation(),t.stopImmediatePropagation()),t.type===`click`&&r?.(t))};for(let e of t)document.addEventListener(e,o,!0);n.set(e,o)}function i(e){let r=n.get(e);if(r){for(let e of t)document.removeEventListener(e,r,!0);n.delete(e)}}function a(e,t){let n=e.wrapper.getBoundingClientRect();return{width:n.width+t,height:n.height+t,realWidth:n.width,realHeight:n.height}}function o(e,t){let{elementDimensions:n,popoverDimensions:r,popoverPadding:i,popoverArrowDimensions:a}=t;return e===`start`?Math.max(Math.min(n.top-i,window.innerHeight-r.realHeight-a.width),a.width):e===`end`?Math.max(Math.min(n.top-r?.realHeight+n.height+i,window.innerHeight-r?.realHeight-a.width),a.width):e===`center`?Math.max(Math.min(n.top+n.height/2-r?.realHeight/2,window.innerHeight-r?.realHeight-a.width),a.width):0}function s(e,t){let{elementDimensions:n,popoverDimensions:r,popoverPadding:i,popoverArrowDimensions:a}=t;return e===`start`?Math.max(Math.min(n.left-i,window.innerWidth-r.realWidth-a.width),a.width):e===`end`?Math.max(Math.min(n.left-r?.realWidth+n.width+i,window.innerWidth-r?.realWidth-a.width),a.width):e===`center`?Math.max(Math.min(n.left+n.width/2-r?.realWidth/2,window.innerWidth-r?.realWidth-a.width),a.width):0}function c(e,t,n){let{align:r,side:i}=n,c=n.centered?`over`:i,l=n.padding,u=a(e,n.offset),d=e.arrow.getBoundingClientRect(),p=t.getBoundingClientRect(),m=p.top-u.height,h=m>=0,g=window.innerHeight-(p.bottom+u.height),_=g>=0,v=p.left-u.width,y=v>=0,b=window.innerWidth-(p.right+u.width),x=b>=0,S=!h&&!_&&!y&&!x,C=c;if(c===`top`&&h?x=y=_=!1:c===`bottom`&&_?x=y=h=!1:c===`left`&&y?x=h=_=!1:c===`right`&&x&&(y=h=_=!1),c===`over`){let t=window.innerWidth/2-u.realWidth/2,n=window.innerHeight/2-u.realHeight/2;e.wrapper.style.left=`${t}px`,e.wrapper.style.right=`auto`,e.wrapper.style.top=`${n}px`,e.wrapper.style.bottom=`auto`}else if(S){let t=window.innerWidth/2-u?.realWidth/2;e.wrapper.style.left=`${t}px`,e.wrapper.style.right=`auto`,e.wrapper.style.bottom=`10px`,e.wrapper.style.top=`auto`}else if(y){let t=Math.min(v,window.innerWidth-u?.realWidth-d.width),n=o(r,{elementDimensions:p,popoverDimensions:u,popoverPadding:l,popoverArrowDimensions:d});e.wrapper.style.left=`${t}px`,e.wrapper.style.top=`${n}px`,e.wrapper.style.bottom=`auto`,e.wrapper.style.right=`auto`,C=`left`}else if(x){let t=Math.min(b,window.innerWidth-u?.realWidth-d.width),n=o(r,{elementDimensions:p,popoverDimensions:u,popoverPadding:l,popoverArrowDimensions:d});e.wrapper.style.right=`${t}px`,e.wrapper.style.top=`${n}px`,e.wrapper.style.bottom=`auto`,e.wrapper.style.left=`auto`,C=`right`}else if(h){let t=Math.min(m,window.innerHeight-u.realHeight-d.width),n=s(r,{elementDimensions:p,popoverDimensions:u,popoverPadding:l,popoverArrowDimensions:d});e.wrapper.style.top=`${t}px`,e.wrapper.style.left=`${n}px`,e.wrapper.style.bottom=`auto`,e.wrapper.style.right=`auto`,C=`top`}else if(_){let t=Math.min(g,window.innerHeight-u?.realHeight-d.width),n=s(r,{elementDimensions:p,popoverDimensions:u,popoverPadding:l,popoverArrowDimensions:d});e.wrapper.style.left=`${n}px`,e.wrapper.style.bottom=`${t}px`,e.wrapper.style.top=`auto`,e.wrapper.style.right=`auto`,C=`bottom`}f(e,S?`over`:C,r,t),[...e.wrapper.classList].filter(e=>e.startsWith(`driver-popover-side-`)||e.startsWith(`driver-popover-align-`)).forEach(t=>e.wrapper.classList.remove(t)),e.wrapper.classList.add(`driver-popover-side-${C}`),e.wrapper.classList.add(`driver-popover-align-${r}`)}function l(e,t,n,r,i,a=10){let o=r-n;return e<=n&&t>=r?i===`start`?15+a/2:i===`end`?o-15-a/2:o/2:(Math.min(Math.max(e,n),r)+Math.min(Math.max(t,n),r))/2-n}function u(e,t,n=10){let r=t-15-n;if(r<15)return Math.max(0,(t-n)/2);let i=e-n/2;return Math.min(Math.max(i,15),r)}function d(e,t,n){return e===`left`||e===`right`?t.bottom>n.top&&t.topn.left&&t.lefte===`auto`||e===`scroll`)}function h(e,t,n,r){return(e/=r/2)<1?n/2*e*e+t:-n/2*(--e*(e-2)-1)+t}function g(e){let t=`a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])`;return e.flatMap(e=>{let n=e.matches(t),r=Array.from(e.querySelectorAll(t));return[...n?[e]:[],...r]}).filter(e=>getComputedStyle(e).pointerEvents!==`none`&&x(e))}function _(e,t){if(!e||y(e))return;let n=e.offsetHeight>window.innerHeight;e.scrollIntoView({behavior:!t||v(e)?`auto`:`smooth`,inline:`center`,block:n?`start`:`center`})}function v(e){if(!e||!e.parentElement)return;let t=e.parentElement;return t.scrollHeight>t.clientHeight}function y(e){let t=e.getBoundingClientRect();return t.top>=0&&t.left>=0&&t.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&t.right<=(window.innerWidth||document.documentElement.clientWidth)&&!b(e,t)}function b(e,t){let n=e;for(;n;){if(getComputedStyle(n).position===`fixed`)return!1;let e=n.parentElement;if(!e||e===document.body||e===document.documentElement)return!1;if(m(e)){let n=e.getBoundingClientRect(),r=n.top+e.clientTop,i=n.left+e.clientLeft;if(t.topr+(e.clientHeight||n.height)||t.right>i+(e.clientWidth||n.width))return!0}n=e}return!1}function x(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)}let S=new WeakSet;function C(e){let t=e.closest(`dialog[open]`);if(!t)return document.body;if(!(`showPopover`in t))return t;let n=t.querySelector(`:scope > .driver-layer`);return n||(n=document.createElement(`div`),n.className=`driver-layer`,n.setAttribute(`popover`,`manual`),t.appendChild(n)),S.has(t)||(S.add(t),t.addEventListener(`close`,()=>{t.querySelector(`:scope > .driver-layer:popover-open`)?.hidePopover()})),n.matches(`:popover-open`)||n.showPopover(),n}function w(e){let t=e.parentElement;e.remove(),t?.classList.contains(`driver-layer`)&&!t.childElementCount&&t.remove()}function T(e){e&&(e.wrapper.style.display=`none`)}function ee(e,t){let n=D();C(e).appendChild(n.wrapper);let{title:i,description:a,showButtons:o,disableButtons:s,showProgress:l,nextBtnText:u,prevBtnText:d,progressText:f}=t;n.nextButton.innerHTML=u,n.previousButton.innerHTML=d,n.progress.innerHTML=f,n.closeButton.setAttribute(`aria-label`,t.closeBtnLabel),t.doneButton&&n.nextButton.classList.add(`driver-popover-done-btn`),i?(n.title.innerHTML=i,n.title.style.display=`block`):n.title.style.display=`none`,a?(n.description.innerHTML=a,n.description.style.display=`block`):n.description.style.display=`none`;let p=o.includes(`next`)||o.includes(`previous`)||l;n.closeButton.style.display=o.includes(`close`)?`block`:`none`,p?(n.footer.style.display=`flex`,n.progress.style.display=l?`block`:`none`,n.nextButton.style.display=o.includes(`next`)?`block`:`none`,n.previousButton.style.display=o.includes(`previous`)?`block`:`none`):n.footer.style.display=`none`,s.includes(`next`)&&(n.nextButton.disabled=!0,n.nextButton.classList.add(`driver-popover-btn-disabled`)),s.includes(`previous`)&&(n.previousButton.disabled=!0,n.previousButton.classList.add(`driver-popover-btn-disabled`)),s.includes(`close`)&&(n.closeButton.disabled=!0,n.closeButton.classList.add(`driver-popover-btn-disabled`));let m=n.wrapper;m.style.display=`block`,m.style.left=``,m.style.top=``,m.style.bottom=``,m.style.right=``,m.id=`driver-popover-content`,m.setAttribute(`role`,`dialog`),m.setAttribute(`aria-labelledby`,`driver-popover-title`),m.setAttribute(`aria-describedby`,`driver-popover-description`);let h=n.arrow;h.className=`driver-popover-arrow`,m.className=`driver-popover ${t.popoverClass||``}`.trim(),r(n.wrapper,e=>{let n=e.target instanceof Element?e.target:e.target?.parentElement;if(n){if(n.closest(`.driver-popover-next-btn`))return t.onNextClick?.();if(n.closest(`.driver-popover-prev-btn`))return t.onPrevClick?.();if(n.closest(`.driver-popover-close-btn`))return t.onCloseClick?.()}},e=>n.description.contains(e)||n.title.contains(e)?!1:!!e.closest(`.driver-popover-prev-btn, .driver-popover-next-btn, .driver-popover-close-btn`)),t.onRender?.(n),c(n,e,t.position),E(n,e,t.position),_(m,t.smoothScroll);let v=g([m,e]);return v.length>0&&v[0].focus(),n}function E(e,t,n){e.wrapper.querySelectorAll(`img`).forEach(r=>{if(r.complete)return;let i=()=>c(e,t,n);r.addEventListener(`load`,i,{once:!0}),r.addEventListener(`error`,i,{once:!0})})}function D(){let e=document.createElement(`div`);e.classList.add(`driver-popover`);let t=document.createElement(`div`);t.classList.add(`driver-popover-arrow`);let n=document.createElement(`div`);n.id=`driver-popover-title`,n.classList.add(`driver-popover-title`),n.style.display=`none`,n.innerText=`Popover Title`;let r=document.createElement(`div`);r.id=`driver-popover-description`,r.classList.add(`driver-popover-description`),r.style.display=`none`,r.innerText=`Popover description is here`;let i=document.createElement(`button`);i.type=`button`,i.classList.add(`driver-popover-close-btn`),i.innerHTML=`×`;let a=document.createElement(`div`);a.classList.add(`driver-popover-footer`);let o=document.createElement(`span`);o.classList.add(`driver-popover-progress-text`),o.innerText=``;let s=document.createElement(`span`);s.classList.add(`driver-popover-navigation-btns`);let c=document.createElement(`button`);c.type=`button`,c.classList.add(`driver-popover-prev-btn`,`driver-popover-footer-btn`),c.innerHTML=`Previous`;let l=document.createElement(`button`);return l.type=`button`,l.classList.add(`driver-popover-next-btn`,`driver-popover-footer-btn`),l.innerHTML=`Next`,s.appendChild(c),s.appendChild(l),a.appendChild(o),a.appendChild(s),e.appendChild(i),e.appendChild(t),e.appendChild(n),e.appendChild(r),e.appendChild(a),{wrapper:e,arrow:t,title:n,description:r,footer:a,previousButton:c,nextButton:l,closeButton:i,footerButtons:s,progress:o}}function O(e){e&&(i(e.wrapper),w(e.wrapper))}function k(e,t){let n=window.innerWidth,r=window.innerHeight,i=t.padding,a=t.radius,o=e.width+i*2,s=e.height+i*2,c=Math.min(a,o/2,s/2),l=Math.floor(Math.max(c,0)),u=e.x-i+l,d=e.y-i,f=o-l*2,p=s-l*2;return`M${n},0L0,0L0,${r}L${n},${r}L${n},0Z
+ M${u},${d} h${f} a${l},${l} 0 0 1 ${l},${l} v${p} a${l},${l} 0 0 1 -${l},${l} h-${f} a${l},${l} 0 0 1 -${l},-${l} v-${p} a${l},${l} 0 0 1 ${l},-${l} z`}function te(e,t,n,r,i){let a=e.getState(`__activeStagePosition`),o=a||r.getBoundingClientRect(),s=i.getBoundingClientRect();a={x:h(t,o.x,s.x-o.x,n),y:h(t,o.y,s.y-o.y,n),width:h(t,o.width,s.width-o.width,n),height:h(t,o.height,s.height-o.height,n)},M(e,a),e.setState(`__activeStagePosition`,a)}function A(e,t){if(!t)return;let n=t.getBoundingClientRect(),r={x:n.x,y:n.y,width:n.width,height:n.height};e.setState(`__activeStagePosition`,r),M(e,r)}function j(e){let t=e.getState(`__activeStagePosition`),n=e.getState(`__overlaySvg`);if(!t)return;if(!n){console.warn(`No stage svg found.`);return}let r=window.innerWidth,i=window.innerHeight;n.setAttribute(`viewBox`,`0 0 ${r} ${i}`)}function ne(e,t){let n=re(e,t);document.body.appendChild(n),r(n,t=>{t.target.tagName===`path`&&e.emit(`overlayClick`)}),e.setState(`__overlaySvg`,n)}function M(e,t){let n=e.getState(`__overlaySvg`);if(!n){ne(e,t);return}let r=n.firstElementChild;if(r?.tagName!==`path`)throw Error(`no path element found in stage svg`);r.setAttribute(`d`,k(t,N(e)))}function N(e){return e.getState(`activeElement`)?.id===`driver-dummy-element`?{padding:0,radius:0}:{padding:e.getConfig(`stagePadding`)||0,radius:e.getConfig(`stageRadius`)||0}}function re(e,t){let n=window.innerWidth,r=window.innerHeight,i=document.createElementNS(`http://www.w3.org/2000/svg`,`svg`);i.classList.add(`driver-overlay`,`driver-overlay-animated`),i.setAttribute(`viewBox`,`0 0 ${n} ${r}`),i.setAttribute(`xmlSpace`,`preserve`),i.setAttribute(`xmlnsXlink`,`http://www.w3.org/1999/xlink`),i.setAttribute(`version`,`1.1`),i.setAttribute(`preserveAspectRatio`,`xMinYMin slice`),i.style.fillRule=`evenodd`,i.style.clipRule=`evenodd`,i.style.strokeLinejoin=`round`,i.style.strokeMiterlimit=`2`,i.style.zIndex=`10000`,i.style.position=`fixed`,i.style.top=`0`,i.style.left=`0`,i.style.width=`100%`,i.style.height=`100%`;let a=document.createElementNS(`http://www.w3.org/2000/svg`,`path`);return a.setAttribute(`d`,k(t,N(e))),a.style.fill=e.getConfig(`overlayColor`)||`rgb(0,0,0)`,a.style.opacity=`${e.getConfig(`overlayOpacity`)}`,a.style.pointerEvents=`auto`,a.style.cursor=`auto`,i.appendChild(a),i}function P(e){let t=e.getState(`__overlaySvg`);t&&(i(t),t.remove())}let F=`{{current}} of {{total}}`;function I(e,t){return!(t.skipMissingElement??e.getConfig(`skipMissingElement`))||!t.element?!1:!p(t.element)}function L(e,t,n){let r=e.getConfig(`steps`)||[];for(let i=t;i>=0&&i!f?.length||f.includes(e)),m=a.onNextClick||e.getConfig(`onNextClick`),h=a.onPrevClick||e.getConfig(`onPrevClick`),g=a.onCloseClick||e.getConfig(`onCloseClick`);return{...i,popover:{showButtons:p,nextBtnText:o?void 0:c,disableButtons:[...e.getConfig(`disableButtons`)||[],...s?[]:[`previous`]],showProgress:u,onNextClick:m||n.onNextClick,onPrevClick:h||n.onPrevClick,onCloseClick:g||n.onCloseClick,...a,progressText:d}}}function V(e,t){return{showButtons:t?.popover?.showButtons||e.getConfig(`showButtons`)||[],disableButtons:t?.popover?.disableButtons||e.getConfig(`disableButtons`)||[]}}function H(e,t,n){let r=e.getConfig(`stagePadding`)||0;return{side:n.popover?.side||`bottom`,align:n.popover?.align||`start`,offset:r+(e.getConfig(`popoverOffset`)||0),padding:r,centered:t.id===`driver-dummy-element`}}function U(e,t,n){let r=n.popover||{},i=e.getState(`activeIndex`),a=i!==void 0&&L(e,i+1,1)===void 0;return{title:r.title,description:r.description,...V(e,n),showProgress:r.showProgress||e.getConfig(`showProgress`)||!1,progressText:r.progressText??(e.getConfig(`progressText`)||F),nextBtnText:r.nextBtnText??(e.getConfig(`nextBtnText`)||`Next`),prevBtnText:r.prevBtnText??(e.getConfig(`prevBtnText`)||`Previous`),closeBtnLabel:r.closeBtnLabel??(e.getConfig(`closeBtnLabel`)||`Close`),doneButton:a,popoverClass:r.popoverClass||e.getConfig(`popoverClass`)||``,smoothScroll:e.getConfig(`smoothScroll`),onNextClick:()=>{a&&e.setState(`__destroyReason`,`done`);let r=R(e,n);return r?r(t,n,e.getHookOpts()):e.emit(`nextClick`)},onPrevClick:()=>{let r=z(e,n);return r?r(t,n,e.getHookOpts()):e.emit(`prevClick`)},onCloseClick:()=>{e.setState(`__destroyReason`,`close`);let r=ie(e,n);return r?r(t,n,e.getHookOpts()):e.emit(`closeClick`)},onRender:t=>{e.setState(`popover`,t),(r.onPopoverRender||e.getConfig(`onPopoverRender`))?.(t,e.getHookOpts())},position:H(e,t,n)}}function W(e,t,n){O(e.getState(`popover`)),ee(t,U(e,t,n))}function G(e,t,n){let r=e.getState(`popover`);r&&c(r,t,H(e,t,n))}let K={"aria-haspopup":`dialog`,"aria-expanded":`true`,"aria-controls":`driver-popover-content`},q=new WeakMap;function J(e){if(!q.has(e)){let t=Object.fromEntries(Object.keys(K).map(t=>[t,e.getAttribute(t)]));q.set(e,t)}for(let[t,n]of Object.entries(K))e.setAttribute(t,n)}function Y(e){let t=q.get(e);if(t){for(let[n,r]of Object.entries(t))r===null?e.removeAttribute(n):e.setAttribute(n,r);q.delete(e)}}function ae(){let e=document.getElementById(`driver-dummy-element`);if(e)return e;let t=document.createElement(`div`);return t.id=`driver-dummy-element`,t.style.width=`0`,t.style.height=`0`,t.style.pointerEvents=`none`,t.style.opacity=`0`,t.style.position=`fixed`,t.style.top=`50%`,t.style.left=`50%`,document.body.appendChild(t),t}function X(e,t){let n=p(t.element);n||(n=ae()),se(e,n,t)}function oe(e){let t=e.getState(`__activeElement`),n=e.getState(`__activeStep`);t&&(A(e,t),j(e),G(e,t,n))}function se(e,t,n){let r=e.getConfig(`duration`)||400,i=Date.now(),a=e.getState(`__activeStep`),o=e.getState(`__activeElement`)||t,s=!o||o===t,c=t.id===`driver-dummy-element`,l=o.id===`driver-dummy-element`,u=e.getConfig(`animate`),d=n.onHighlightStarted||e.getConfig(`onHighlightStarted`),f=n?.onHighlighted||e.getConfig(`onHighlighted`),p=a?.onDeselected||e.getConfig(`onDeselected`),h=e.getHookOpts();a&&a!==n&&p&&p(l?void 0:o,a,h),d&&d(c?void 0:t,n,h);let g=!s&&u,v=!1;T(e.getState(`popover`)),e.setState(`previousStep`,a),e.setState(`previousElement`,o),e.setState(`activeStep`,n),e.setState(`activeElement`,t);let y=()=>{if(e.getState(`__transitionCallback`)!==y)return;let s=Date.now()-i,l=r-s<=r/2;n.popover&&l&&!v&&g&&(W(e,t,n),v=!0),e.getConfig(`animate`)&&s{e.classList.remove(`driver-active-element-parent`,`driver-active-element-parent-no-scroll`)}),_(t,e.getConfig(`smoothScroll`)),!g&&n.popover&&W(e,t,n),document.querySelectorAll(`.driver-active-element`).forEach(e=>{e.classList.remove(`driver-active-element`,`driver-no-interaction`),Y(e)}),(n.disableActiveInteraction??e.getConfig(`disableActiveInteraction`))&&t.classList.add(`driver-no-interaction`);let b=t.parentElement;b&&b!==document.body&&(b.classList.add(`driver-active-element-parent`),m(b)&&b.classList.add(`driver-active-element-parent-no-scroll`)),t.classList.add(`driver-active-element`),t.id!==`driver-dummy-element`&&J(t)}function ce(){document.getElementById(`driver-dummy-element`)?.remove(),document.querySelectorAll(`.driver-active-element`).forEach(e=>{let t=e.parentElement;t&&t!==document.body&&t.classList.remove(`driver-active-element-parent`,`driver-active-element-parent-no-scroll`),e.classList.remove(`driver-active-element`,`driver-no-interaction`),Y(e)})}function Z(e){let t=e.getState(`__resizeTimeout`);t&&window.cancelAnimationFrame(t),e.setState(`__resizeTimeout`,window.requestAnimationFrame(()=>oe(e)))}function le(e,t){if(!e.getState(`isInitialized`)||!(t.key===`Tab`||t.keyCode===9))return;let n=e.getState(`__activeElement`),r=e.getState(`popover`)?.wrapper,i=g([...r?[r]:[],...n?[n]:[]]),a=i[0],o=i[i.length-1];t.preventDefault(),t.shiftKey?(i[i.indexOf(document.activeElement)-1]||o)?.focus():(i[i.indexOf(document.activeElement)+1]||a)?.focus()}function ue(e,t){(e.getConfig(`allowKeyboardControl`)??!0)&&(t.key===`Escape`?e.emit(`escapePress`):t.key===`ArrowRight`?e.emit(`arrowRightPress`):t.key===`ArrowLeft`&&e.emit(`arrowLeftPress`))}function de(e,t){let n=e.getState(`__activeElement`),r=t.target;!n||!r||!n.contains(r)||e.emit(`activeElementClick`)}function fe(e){let t=t=>ue(e,t),n=t=>le(e,t),r=()=>Z(e),i=()=>Z(e),a=t=>de(e,t);e.setState(`__events`,{onKeyup:t,onKeydown:n,onResize:r,onScroll:i,onClick:a}),window.addEventListener(`keyup`,t,!1),window.addEventListener(`keydown`,n,!1),window.addEventListener(`resize`,r),window.addEventListener(`scroll`,i),document.addEventListener(`click`,a,!1)}function pe(e){let t=e.getState(`__events`);t&&(window.removeEventListener(`keyup`,t.onKeyup),window.removeEventListener(`keydown`,t.onKeydown),window.removeEventListener(`resize`,t.onResize),window.removeEventListener(`scroll`,t.onScroll),document.removeEventListener(`click`,t.onClick,!1))}function Q(){let e={};function t(t={}){e={animate:!0,duration:400,allowClose:!0,allowScroll:!0,overlayClickBehavior:`close`,overlayOpacity:.7,smoothScroll:!1,disableActiveInteraction:!1,advanceOnClick:!1,skipMissingElement:!1,waitForElement:0,showProgress:!1,stagePadding:10,stageRadius:5,popoverOffset:10,showButtons:[`next`,`previous`,`close`],disableButtons:[],overlayColor:`#000`,...t}}return t(),{getConfig:(t=>t?e[t]:e),configure:t}}function me(){let e={},t=(t=>t?e[t]:e),n=(t,n)=>{e[t]=n};function r(){e={}}return{getState:t,setState:n,resetState:r}}function he(){let e={};function t(t,n){e[t]=n}function n(t){e[t]?.()}function r(){e={}}return{listen:t,emit:n,reset:r}}function ge(e={}){let t=Q();t.configure(e);let n=me(),r=he(),i;return{getConfig:t.getConfig,setConfig:t.configure,getState:n.getState,setState:n.setState,resetState:n.resetState,listen:r.listen,emit:r.emit,resetEmitter:r.reset,getDriver:()=>i,setDriver:e=>{i=e},getHookOpts:(e,r)=>{let a=e||n.getState();return{config:t.getConfig(),state:a,driver:i,index:a.activeIndex,...r?{reason:r}:{}}}}}let $;function _e(e={}){let t=ge(e),n=()=>y(`replaced`,!1);function r(e=`close`){t.getConfig(`allowClose`)&&y(e)}function i(){let e=t.getState(`activeIndex`);e!==void 0&&L(t,e+1,1)===void 0&&t.setState(`__destroyReason`,`done`)}function a(){let{disableButtons:e}=V(t,t.getState(`__activeStep`));e.includes(`close`)||r(`escape`)}function o(e){let{showButtons:n,disableButtons:r}=V(t,t.getState(`__activeStep`));return n.includes(e)&&!r.includes(e)}function s(){let e=t.getConfig(`overlayClickBehavior`);if(t.getConfig(`allowClose`)&&e===`close`){y(`overlay`);return}if(typeof e==`function`){t.setState(`__destroyReason`,`overlay`);let n=t.getState(`__activeStep`);e(t.getState(`__activeElement`),n,t.getHookOpts());return}if(e===`nextStep`){let e=t.getState(`activeStep`),n=t.getState(`activeElement`),r=R(t,e);if(r){i(),r(n,e,t.getHookOpts());return}c()}}function c(){let e=t.getState(`activeIndex`),n=t.getConfig(`steps`)||[];if(e===void 0)return;let r=e+1;n[r]?v(r):y(`done`)}function l(){let e=t.getState(`activeIndex`),n=t.getConfig(`steps`)||[];if(e===void 0)return;let r=e-1;n[r]?v(r):y(`api`)}function u(e){(t.getConfig(`steps`)||[])[e]?v(e):y(`api`)}function d(){if(t.getState(`__transitionCallback`))return;let e=t.getState(`__activeStep`);if(!e||!(e.advanceOnClick??t.getConfig(`advanceOnClick`)))return;let n=t.getState(`__activeElement`),r=R(t,e);if(r){i(),r(n,e,t.getHookOpts());return}c()}function f(){if(t.getState(`__transitionCallback`))return;let e=t.getState(`activeIndex`),n=t.getState(`__activeStep`),r=t.getState(`__activeElement`);if(e===void 0||n===void 0||!(t.getConfig(`steps`)||[])[e-1]||!o(`previous`))return;let i=z(t,n);if(i)return i(r,n,t.getHookOpts());l()}function m(){if(t.getState(`__transitionCallback`))return;let e=t.getState(`activeIndex`),n=t.getState(`__activeStep`),r=t.getState(`__activeElement`);if(e===void 0||n===void 0||!o(`next`))return;let a=R(t,n);if(a)return i(),a(r,n,t.getHookOpts());c()}function h(){t.getState(`isInitialized`)||($&&$!==n&&$(),$=n,t.setState(`isInitialized`,!0),document.body.classList.add(`driver-active`,t.getConfig(`animate`)?`driver-fade`:`driver-simple`),t.getConfig(`allowScroll`)||document.body.classList.add(`driver-no-scroll`),document.body.style.setProperty(`--driver-animation-duration`,`${t.getConfig(`duration`)||400}ms`),fe(t),t.listen(`overlayClick`,s),t.listen(`activeElementClick`,d),t.listen(`escapePress`,a),t.listen(`closeClick`,r),t.listen(`arrowLeftPress`,f),t.listen(`arrowRightPress`,m))}function g(){let e=t.getState(`__pendingWaitCancel`);e&&(t.setState(`__pendingWaitCancel`,void 0),e())}function _(e,n,r){let i=()=>{a.disconnect(),window.clearTimeout(o),t.setState(`__pendingWaitCancel`,void 0),r()},a=new MutationObserver(()=>{p(e.element)&&i()}),o=window.setTimeout(i,n);t.setState(`__pendingWaitCancel`,()=>{a.disconnect(),window.clearTimeout(o)}),a.observe(document.documentElement,{childList:!0,subtree:!0,attributes:!0})}function v(e=0,n=!1){g(),t.setState(`__destroyReason`,void 0);let r=t.getConfig(`steps`);if(!r){console.error(`No steps to drive through`),y(`api`);return}if(!r[e]){y(`api`);return}let i=r[e],a=i.waitForElement??t.getConfig(`waitForElement`)??0;if(!n&&a>0&&i.element&&!p(i.element)){T(t.getState(`popover`)),_(i,a,()=>v(e,!0));return}if(I(t,i)){let n=t.getState(`activeIndex`),i=typeof n==`number`&&e{o?v(e+1):y(`done`)},onPrevClick:()=>{v(e-1)},onCloseClick:()=>{y(`close`)}}))}function y(e,r=!0){if(!t.getState(`isInitialized`))return;e===`api`&&(e=t.getState(`__destroyReason`)??`api`);let i=t.getState(`__activeElement`),a=t.getState(`__activeStep`),o=t.getState(`__activeOnDestroyed`),s=t.getConfig(`onDestroyStarted`);if(r&&s){t.setState(`__destroyReason`,e),s(!i||i?.id===`driver-dummy-element`?void 0:i,a,t.getHookOpts(void 0,e));return}$===n&&($=void 0);let c=a?.onDeselected||t.getConfig(`onDeselected`),l=t.getConfig(`onDestroyed`);document.body.classList.remove(`driver-active`,`driver-fade`,`driver-simple`,`driver-no-scroll`),document.body.style.removeProperty(`--driver-animation-duration`),g(),pe(t),O(t.getState(`popover`)),ce(),P(t),t.resetEmitter();let u=t.getState();if(t.resetState(),i&&a){let n=i.id===`driver-dummy-element`;c&&c(n?void 0:i,a,t.getHookOpts(u,e)),l&&l(n?void 0:i,a,t.getHookOpts(u,e))}o&&o.focus()}let b={isActive:()=>t.getState(`isInitialized`)||!1,refresh:()=>Z(t),drive:(e=0)=>{h(),v(e)},setConfig:t.setConfig,setSteps:e=>{g(),t.resetState(),t.setConfig({...t.getConfig(),steps:e})},getConfig:t.getConfig,getState:t.getState,getActiveIndex:()=>t.getState(`activeIndex`),isFirstStep:()=>{let e=t.getState(`activeIndex`);return e!==void 0&&L(t,e-1,-1)===void 0},isLastStep:()=>{let e=t.getState(`activeIndex`);return e!==void 0&&L(t,e+1,1)===void 0},getActiveStep:()=>t.getState(`activeStep`),getActiveElement:()=>t.getState(`activeElement`),getPreviousElement:()=>t.getState(`previousElement`),getPreviousStep:()=>t.getState(`previousStep`),getNextStep:()=>{let e=t.getConfig(`steps`)||[],n=t.getState(`activeIndex`);if(n===void 0)return;let r=L(t,n+1,1);return r===void 0?void 0:e[r]},moveNext:c,movePrevious:l,moveTo:u,hasNextStep:()=>{let e=t.getState(`activeIndex`);return e!==void 0&&L(t,e+1,1)!==void 0},hasPreviousStep:()=>{let e=t.getState(`activeIndex`);return e!==void 0&&L(t,e-1,-1)!==void 0},highlight:e=>{h(),t.setState(`__destroyReason`,void 0),X(t,{...e,popover:e.popover?{showButtons:[],showProgress:!1,progressText:``,...e.popover}:void 0})},destroy:()=>{y(`api`,!1)}};return t.setDriver(b),b}return e.driver=_e,e})({});
\ No newline at end of file
diff --git a/lib/driverjs/license b/lib/driverjs/license
new file mode 100644
index 0000000..7ceaeef
--- /dev/null
+++ b/lib/driverjs/license
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) Kamran Ahmed
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
index 5052933..90f287c 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Exploratory Testing Extension",
- "version": "4.1.1",
+ "version": "4.2.0",
"description": "Chrome extension for exploratory testing",
"permissions": [
"storage",
diff --git a/package.json b/package.json
index 01f14b1..160582d 100644
--- a/package.json
+++ b/package.json
@@ -19,13 +19,13 @@
"scripts": {
"test": "jest",
"test:e2e": "playwright test smoke.spec.js basic-functionality.spec.js",
- "test:e2e:all": "playwright test smoke.spec.js basic-functionality.spec.js reports-export.spec.js crop-screenshot.spec.js annotation-editor.spec.js",
+ "test:e2e:all": "playwright test smoke.spec.js basic-functionality.spec.js reports-export.spec.js crop-screenshot.spec.js annotation-editor.spec.js tutorial.spec.js",
"test:e2e:headed": "cross-env HEADED=true playwright test smoke.spec.js basic-functionality.spec.js",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test smoke.spec.js basic-functionality.spec.js --debug",
"test:e2e:report": "playwright show-report",
"test:e2e:crop": "cross-env HEADED=true playwright test crop-screenshot.spec.js",
- "test:e2e:ci": "playwright test smoke.spec.js basic-functionality.spec.js reports-export.spec.js crop-screenshot.spec.js annotation-editor.spec.js",
+ "test:e2e:ci": "playwright test smoke.spec.js basic-functionality.spec.js reports-export.spec.js crop-screenshot.spec.js annotation-editor.spec.js tutorial.spec.js",
"test:all": "npm test && npm run test:e2e:all"
},
"repository": {
diff --git a/popup.html b/popup.html
index 08e988f..4b600fe 100644
--- a/popup.html
+++ b/popup.html
@@ -12,8 +12,9 @@
-