mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-03 01:18:32 +00:00
Compare commits
3 Commits
feat/test-
...
feat/fix-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd8bc459ce | ||
|
|
a93e1dc8bf | ||
|
|
4fffef51ba |
4
.github/workflows/tests-linux.yml
vendored
4
.github/workflows/tests-linux.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
e2e-test:
|
||||
name: Playwright E2E Tests (Linux)
|
||||
timeout-minutes: 180
|
||||
timeout-minutes: 240
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
sudo chown root node_modules/electron/dist/chrome-sandbox
|
||||
sudo chmod 4755 node_modules/electron/dist/chrome-sandbox
|
||||
|
||||
- name: Run playwright Tests
|
||||
- name: Run E2E Tests
|
||||
uses: ./.github/actions/tests/run-e2e-tests
|
||||
with:
|
||||
os: ubuntu
|
||||
|
||||
2
.github/workflows/tests-macos.yml
vendored
2
.github/workflows/tests-macos.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
e2e-test:
|
||||
name: Playwright E2E Tests (macOS)
|
||||
timeout-minutes: 180
|
||||
timeout-minutes: 240
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
2
.github/workflows/tests-windows.yml
vendored
2
.github/workflows/tests-windows.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
e2e-test:
|
||||
name: Playwright E2E Tests (Windows)
|
||||
timeout-minutes: 180
|
||||
timeout-minutes: 240
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -4,6 +4,8 @@ const StyledWrapper = styled.div`
|
||||
color: ${(props) => props.theme.colors.danger};
|
||||
pre {
|
||||
color: ${(props) => props.theme.colors.danger};
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import React, { memo } from 'react';
|
||||
import semver from 'semver';
|
||||
|
||||
const DEFAULT_VERSION = 'v1.0.0';
|
||||
|
||||
/**
|
||||
* Normalise a raw collection version for display: coerce partials to a full
|
||||
* major.minor.patch ("1" -> "v1.0.0", "2.1" -> "v2.1.0"), keep a single "v"
|
||||
* prefix, preserve pre-releases ("1.0.0-beta" -> "v1.0.0-beta"), and fall back to
|
||||
* the default when the version is unset or unparseable.
|
||||
*/
|
||||
const formatVersion = (version) => {
|
||||
const coerced = semver.coerce(version, { includePrerelease: true });
|
||||
return coerced ? `v${coerced.version}` : DEFAULT_VERSION;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read-only display of the collection's current version and a summary of its
|
||||
* contents (folder + request counts). Presentational and prop-driven so it can be
|
||||
* reused wherever the collection version needs to be shown.
|
||||
*/
|
||||
const CollectionVersionInfo = ({ version, folderCount = 0, requestCount = 0 }) => {
|
||||
const folderLabel = folderCount === 1 ? 'Folder' : 'Folders';
|
||||
const requestLabel = requestCount === 1 ? 'request' : 'requests';
|
||||
|
||||
return (
|
||||
<div className="version-info" data-testid="version-info">
|
||||
<div className="version-line">
|
||||
<span className="version-label">Collection Version:</span>{' '}
|
||||
<span className="version-value" data-testid="version-value">{formatVersion(version)}</span>
|
||||
</div>
|
||||
<p className="version-summary" data-testid="version-summary">
|
||||
{`${folderCount} ${folderLabel} • ${requestCount} ${requestLabel}`}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(CollectionVersionInfo);
|
||||
@@ -0,0 +1,113 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, memo } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import ColorBadge from 'components/ColorBadge';
|
||||
|
||||
// Show at most 5 environments at a glance; the list virtualises and scrolls beyond
|
||||
// that, so it stays performant even for collections with hundreds of environments
|
||||
// (only the visible rows are ever in the DOM).
|
||||
const MAX_VISIBLE_ROWS = 5;
|
||||
|
||||
// Fixed row height (px). MUST stay in sync with the `.env-row` height in StyledWrapper.js,
|
||||
// since it is passed to Virtuoso as `fixedItemHeight`.
|
||||
const ENV_ROW_HEIGHT = 34;
|
||||
|
||||
/**
|
||||
* A selectable, virtualised list of collection environments (checkbox + color dot + name)
|
||||
* with a header that carries the title and a tri-state "select all" checkbox.
|
||||
*
|
||||
* Selection is controlled by the parent via `selectedUids`; `onToggleAll(nextSelectAll)`
|
||||
* fires with the desired state when the header checkbox is clicked. Presentational and
|
||||
* prop-driven so it can be reused wherever an environment multi-select is needed.
|
||||
*/
|
||||
const EnvironmentSelectionList = ({
|
||||
environments = [],
|
||||
selectedUids = [],
|
||||
onToggle,
|
||||
onToggleAll,
|
||||
title = 'Environments',
|
||||
disabled = false
|
||||
}) => {
|
||||
// O(1) membership checks regardless of how many environments are rendered.
|
||||
const selectedSet = useMemo(() => new Set(selectedUids), [selectedUids]);
|
||||
|
||||
const selectedCount = useMemo(
|
||||
() => environments.reduce((count, env) => (selectedSet.has(env?.uid) ? count + 1 : count), 0),
|
||||
[environments, selectedSet]
|
||||
);
|
||||
const allSelected = environments.length > 0 && selectedCount === environments.length;
|
||||
const someSelected = selectedCount > 0 && !allSelected;
|
||||
|
||||
const selectAllRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (selectAllRef.current) {
|
||||
selectAllRef.current.indeterminate = someSelected;
|
||||
}
|
||||
}, [someSelected]);
|
||||
|
||||
const handleToggleAll = useCallback((event) => onToggleAll?.(event.target.checked), [onToggleAll]);
|
||||
|
||||
const computeItemKey = useCallback((_index, env) => env?.uid, []);
|
||||
|
||||
const renderEnvironment = useCallback(
|
||||
(_index, env) => (
|
||||
<label className="env-row" data-testid="env-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="env-checkbox"
|
||||
checked={selectedSet.has(env?.uid)}
|
||||
disabled={disabled}
|
||||
onChange={() => onToggle?.(env?.uid)}
|
||||
data-testid={`env-select-${env?.uid}`}
|
||||
/>
|
||||
<ColorBadge color={env?.color} size={8} />
|
||||
<span className="env-name truncate">{env?.name}</span>
|
||||
</label>
|
||||
),
|
||||
[selectedSet, disabled, onToggle]
|
||||
);
|
||||
|
||||
if (!environments.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fit the list to its content, capped at MAX_VISIBLE_ROWS — beyond that it scrolls.
|
||||
const visibleRows = Math.min(environments.length, MAX_VISIBLE_ROWS);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="env-section-header">
|
||||
<div className="env-section-heading">
|
||||
<h4 className="env-section-title" data-testid="env-section-title">{title}</h4>
|
||||
<span className="env-section-count" data-testid="env-selected-count">
|
||||
({selectedCount}/{environments.length} selected)
|
||||
</span>
|
||||
</div>
|
||||
<label className="env-select-all">
|
||||
<input
|
||||
ref={selectAllRef}
|
||||
type="checkbox"
|
||||
className="env-checkbox"
|
||||
checked={allSelected}
|
||||
disabled={disabled}
|
||||
onChange={handleToggleAll}
|
||||
data-testid="env-select-all"
|
||||
/>
|
||||
<span className="env-select-all-label" data-testid="env-select-all-label">Select All</span>
|
||||
</label>
|
||||
</div>
|
||||
<Virtuoso
|
||||
className="env-list"
|
||||
role="group"
|
||||
aria-label={title}
|
||||
style={{ height: visibleRows * ENV_ROW_HEIGHT }}
|
||||
data={environments}
|
||||
computeItemKey={computeItemKey}
|
||||
itemContent={renderEnvironment}
|
||||
fixedItemHeight={ENV_ROW_HEIGHT}
|
||||
increaseViewportBy={ENV_ROW_HEIGHT * 3}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(EnvironmentSelectionList);
|
||||
@@ -13,27 +13,108 @@ const StyledWrapper = styled.div`
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
.config-card {
|
||||
border: 1px solid ${(props) => props.theme.border.border1};
|
||||
border-radius: ${(props) => props.theme.border.radius.md};
|
||||
overflow: hidden;
|
||||
border: 1px solid ${(props) => props.theme.border.border1};
|
||||
|
||||
.preview-label {
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 500;
|
||||
color: #3b82f6;
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
border: 1px dashed rgba(59, 130, 246, 0.4);
|
||||
border-radius: ${(props) => props.theme.border.radius.sm};
|
||||
.version-info {
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: ${(props) => props.theme.background.mantle};
|
||||
|
||||
.version-line {
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.text};
|
||||
}
|
||||
|
||||
.version-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.version-summary {
|
||||
margin: 0.25rem 0 0;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
.card-divider {
|
||||
height: 1px;
|
||||
background-color: ${(props) => props.theme.border.border1};
|
||||
}
|
||||
|
||||
.env-section {
|
||||
padding: 1rem;
|
||||
|
||||
.env-checkbox {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.env-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.env-section-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.env-section-count {
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.env-section-title {
|
||||
margin: 0;
|
||||
font-size: ${(props) => props.theme.font.size.xs};
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
}
|
||||
|
||||
.env-select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.env-select-all-label {
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.colors.text.muted};
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.env-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
/* Fixed row height — MUST match ENV_ROW_HEIGHT (Virtuoso fixedItemHeight)
|
||||
in EnvironmentSelectionList. The inter-row spacing is baked in here. */
|
||||
height: 34px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
|
||||
.env-name {
|
||||
font-size: ${(props) => props.theme.font.size.sm};
|
||||
color: ${(props) => props.theme.text};
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState, Fragment } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import * as FileSaver from 'file-saver';
|
||||
@@ -8,10 +8,12 @@ import toast from 'react-hot-toast';
|
||||
import { IconBook, IconCheck, IconAlertTriangle, IconLoader2 } from '@tabler/icons';
|
||||
|
||||
import Modal from 'components/Modal';
|
||||
import Portal from 'components/Portal';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import demoImage from './demo.png';
|
||||
import CollectionVersionInfo from './CollectionVersionInfo';
|
||||
import EnvironmentSelectionList from './EnvironmentSelectionList';
|
||||
import { useApp } from 'providers/App';
|
||||
import { transformCollectionToSaveToExportAsFile, findCollectionByUid, areItemsLoading, sortItemsBySidebarOrder } from 'utils/collections/index';
|
||||
import { transformCollectionToSaveToExportAsFile, findCollectionByUid, areItemsLoading, sortItemsBySidebarOrder, getCollectionItemCounts } from 'utils/collections/index';
|
||||
import { brunoToOpenCollection } from '@usebruno/converters';
|
||||
import { sanitizeName } from 'utils/common/regex';
|
||||
import { escapeHtml } from 'utils/response';
|
||||
@@ -51,14 +53,16 @@ const buildHtmlDocument = (collectionName, escapedYamlContent) => `<!DOCTYPE htm
|
||||
</html>`;
|
||||
|
||||
const CollectionNotFound = ({ onClose }) => (
|
||||
<Modal size="md" title="Generate Documentation" confirmText="Close" handleConfirm={onClose} hideCancel>
|
||||
<StyledWrapper className="w-[500px]">
|
||||
<div className="flex items-center gap-2 text-warning">
|
||||
<IconAlertTriangle size={16} className="shrink-0" />
|
||||
<span>Collection not found. It may have been deleted or is no longer available.</span>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</Modal>
|
||||
<Portal>
|
||||
<Modal size="md" title="Generate Documentation" confirmText="Close" handleConfirm={onClose} hideCancel>
|
||||
<StyledWrapper className="w-[500px]">
|
||||
<div className="flex items-center gap-2 text-warning">
|
||||
<IconAlertTriangle size={16} className="shrink-0" />
|
||||
<span>Collection not found. It may have been deleted or is no longer available.</span>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
|
||||
const GenerateDocumentation = ({ onClose, collectionUid }) => {
|
||||
@@ -72,6 +76,43 @@ const GenerateDocumentation = ({ onClose, collectionUid }) => {
|
||||
[collection]
|
||||
);
|
||||
|
||||
// The collection's current version (read-only here); formatted for display below.
|
||||
const currentVersion = collection?.version;
|
||||
|
||||
// Folder + request counts, computed from the collection tree (recursively).
|
||||
const { folderCount, requestCount } = useMemo(
|
||||
() => getCollectionItemCounts(collection?.items),
|
||||
[collection?.items]
|
||||
);
|
||||
|
||||
const environments = useMemo(() => collection?.environments || [], [collection?.environments]);
|
||||
|
||||
// Track *deselected* environments so all environments — including any that load
|
||||
// after mount — stay selected by default, matching the design.
|
||||
const [deselectedEnvUids, setDeselectedEnvUids] = useState(() => new Set());
|
||||
const selectedEnvUids = useMemo(
|
||||
() => environments.filter((env) => !deselectedEnvUids.has(env.uid)).map((env) => env.uid),
|
||||
[environments, deselectedEnvUids]
|
||||
);
|
||||
|
||||
const toggleEnv = useCallback((uid) => {
|
||||
setDeselectedEnvUids((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(uid)) {
|
||||
next.delete(uid);
|
||||
} else {
|
||||
next.add(uid);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Select all -> nothing deselected; deselect all -> every environment deselected.
|
||||
const toggleAllEnvs = useCallback(
|
||||
(selectAll) => setDeselectedEnvUids(selectAll ? new Set() : new Set(environments.map((env) => env.uid))),
|
||||
[environments]
|
||||
);
|
||||
|
||||
const handleGenerate = useCallback(() => {
|
||||
try {
|
||||
const collectionCopy = cloneDeep(collection);
|
||||
@@ -80,9 +121,21 @@ const GenerateDocumentation = ({ onClose, collectionUid }) => {
|
||||
// ) at every depth, so the generated docs match the collection shown in the sidebar.
|
||||
collectionCopy.items = sortItemsBySidebarOrder(collectionCopy.items);
|
||||
|
||||
// Only include the environments the user kept selected in the generated docs.
|
||||
const selectedSet = new Set(selectedEnvUids);
|
||||
collectionCopy.environments = (collectionCopy.environments || []).filter((env) => selectedSet.has(env.uid));
|
||||
|
||||
const transformedCollection = transformCollectionToSaveToExportAsFile(collectionCopy);
|
||||
const openCollection = brunoToOpenCollection(transformedCollection);
|
||||
|
||||
// The docs are generated from the current collection version (when set).
|
||||
if (currentVersion) {
|
||||
openCollection.info = {
|
||||
...openCollection.info,
|
||||
version: currentVersion
|
||||
};
|
||||
}
|
||||
|
||||
openCollection.extensions = {
|
||||
...openCollection.extensions,
|
||||
bruno: {
|
||||
@@ -119,59 +172,74 @@ const GenerateDocumentation = ({ onClose, collectionUid }) => {
|
||||
console.error('Error generating documentation:', error);
|
||||
toast.error('Failed to generate documentation');
|
||||
}
|
||||
}, [collection, version, onClose]);
|
||||
}, [collection, version, onClose, currentVersion, selectedEnvUids]);
|
||||
|
||||
if (!collection) {
|
||||
return <CollectionNotFound onClose={onClose} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="md"
|
||||
title="Generate Documentation"
|
||||
confirmText={isLoading ? 'Loading...' : 'Generate'}
|
||||
cancelText="Cancel"
|
||||
handleConfirm={isLoading ? undefined : handleGenerate}
|
||||
handleCancel={onClose}
|
||||
confirmDisabled={isLoading}
|
||||
>
|
||||
<StyledWrapper className="w-[500px]">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center gap-3 py-8">
|
||||
<IconLoader2 size={20} className="animate-spin" />
|
||||
<span>Loading collection...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="content">
|
||||
<h3 className="title flex items-center gap-2 mt-2 font-medium">
|
||||
<IconBook size={18} />
|
||||
<span>Interactive API Documentation</span>
|
||||
</h3>
|
||||
<p className="description mb-4">
|
||||
Generate a standalone HTML file that can be hosted anywhere or shared with your team.
|
||||
</p>
|
||||
|
||||
<div className="preview-container relative mb-4">
|
||||
<span className="preview-label absolute">Sample Output</span>
|
||||
<img src={demoImage} alt="Documentation preview" className="preview-image" />
|
||||
<Portal>
|
||||
<Modal
|
||||
size="md"
|
||||
title="Generate Documentation"
|
||||
confirmText={isLoading ? 'Loading...' : 'Generate'}
|
||||
cancelText="Cancel"
|
||||
handleConfirm={isLoading ? undefined : handleGenerate}
|
||||
handleCancel={onClose}
|
||||
confirmDisabled={isLoading}
|
||||
>
|
||||
<StyledWrapper className="w-[500px]">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center gap-3 py-8">
|
||||
<IconLoader2 size={20} className="animate-spin" />
|
||||
<span>Loading collection...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="content">
|
||||
<h3 className="title flex items-center gap-2 mt-2 font-medium">
|
||||
<IconBook size={18} />
|
||||
<span>Interactive API Documentation</span>
|
||||
</h3>
|
||||
<p className="description mb-4">
|
||||
Generate a standalone HTML file that can be hosted anywhere or shared with your team.
|
||||
</p>
|
||||
|
||||
<ul className="features flex flex-col list-none gap-2 p-0 mb-4">
|
||||
{FEATURES.map((feature) => (
|
||||
<li key={feature} className="flex items-center gap-2.5">
|
||||
<IconCheck size={16} className="check-icon flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="features flex flex-col list-none gap-2 p-0 mb-4">
|
||||
{FEATURES.map((feature) => (
|
||||
<li key={feature} className="flex items-center gap-2.5">
|
||||
<IconCheck size={16} className="check-icon flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<p className="note m-0">
|
||||
The generated file loads OpenCollection's JavaScript and CSS files from a CDN, which requires an internet connection.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</Modal>
|
||||
<div className="config-card mb-4">
|
||||
<CollectionVersionInfo version={currentVersion} folderCount={folderCount} requestCount={requestCount} />
|
||||
{environments.length > 0 && (
|
||||
<Fragment>
|
||||
<div className="card-divider" />
|
||||
<div className="env-section">
|
||||
<EnvironmentSelectionList
|
||||
title="Environments to include"
|
||||
environments={environments}
|
||||
selectedUids={selectedEnvUids}
|
||||
onToggle={toggleEnv}
|
||||
onToggleAll={toggleAllEnvs}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="note m-0">
|
||||
The generated file loads OpenCollection's JavaScript and CSS files from a CDN, which requires an internet connection.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -888,6 +888,21 @@ export const isItemAFolder = (item) => {
|
||||
return !item.hasOwnProperty('request') && item.type === 'folder';
|
||||
};
|
||||
|
||||
/**
|
||||
* Counts the folders and requests in a collection's item tree, recursively at every
|
||||
* depth. Used to summarise a collection (e.g. in the Generate Documentation modal).
|
||||
*
|
||||
* @param {Array} items - The collection's `items` tree.
|
||||
* @returns {{ folderCount: number, requestCount: number }}
|
||||
*/
|
||||
export const getCollectionItemCounts = (items = []) => {
|
||||
const flattened = flattenItems(items);
|
||||
return {
|
||||
folderCount: flattened.filter(isItemAFolder).length,
|
||||
requestCount: flattened.filter(isItemARequest).length
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Orders a list of collection items exactly the way the Sidebar tree renders them:
|
||||
* folders first (via `sortByNameThenSequence`), then requests ordered by `seq`. The
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const { describe, it, expect } = require('@jest/globals');
|
||||
import { mergeHeaders, transformRequestToSaveToFilesystem } from './index';
|
||||
import { mergeHeaders, transformRequestToSaveToFilesystem, getCollectionItemCounts } from './index';
|
||||
|
||||
describe('mergeHeaders', () => {
|
||||
it('should include headers from collection, folder and request (with correct precedence)', () => {
|
||||
@@ -86,3 +86,49 @@ describe('transformRequestToSaveToFilesystem', () => {
|
||||
expect(transformed.request.headers[0].annotations).toEqual([{ name: 'header-note', value: 'keep me' }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCollectionItemCounts', () => {
|
||||
it('counts folders and requests recursively at every depth', () => {
|
||||
const items = [
|
||||
{
|
||||
type: 'folder',
|
||||
name: 'Zoo',
|
||||
items: [
|
||||
{ type: 'http-request', name: 'Lion', request: {} },
|
||||
{ type: 'graphql-request', name: 'Bear', request: {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'folder',
|
||||
name: 'Aviary',
|
||||
items: [
|
||||
{
|
||||
type: 'folder',
|
||||
name: 'Nest',
|
||||
items: [{ type: 'http-request', name: 'Egg', request: {} }]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ type: 'http-request', name: 'RootReq', request: {} }
|
||||
];
|
||||
|
||||
// Folders: Zoo, Aviary, Nest -> 3. Requests: Lion, Bear, Egg, RootReq -> 4.
|
||||
expect(getCollectionItemCounts(items)).toEqual({ folderCount: 3, requestCount: 4 });
|
||||
});
|
||||
|
||||
it('counts every request transport type', () => {
|
||||
const items = [
|
||||
{ type: 'http-request', request: {} },
|
||||
{ type: 'graphql-request', request: {} },
|
||||
{ type: 'grpc-request', request: {} },
|
||||
{ type: 'ws-request', request: {} }
|
||||
];
|
||||
|
||||
expect(getCollectionItemCounts(items)).toEqual({ folderCount: 0, requestCount: 4 });
|
||||
});
|
||||
|
||||
it('returns zero counts for empty or missing items', () => {
|
||||
expect(getCollectionItemCounts([])).toEqual({ folderCount: 0, requestCount: 0 });
|
||||
expect(getCollectionItemCounts(undefined)).toEqual({ folderCount: 0, requestCount: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ if (process.env.CI) {
|
||||
export default defineConfig({
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 1,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: undefined,
|
||||
reporter,
|
||||
|
||||
|
||||
@@ -163,7 +163,11 @@ export const test = baseTest.extend<
|
||||
async ({ }, use) => {
|
||||
const dirs: string[] = [];
|
||||
await use(async (tag?: string) => {
|
||||
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `pw-${tag || ''}-`));
|
||||
// Strip characters that are illegal in Windows filenames (<>:"/\|?*) and
|
||||
// whitespace, so a descriptive tag (e.g. one derived from a test title
|
||||
// containing quotes) can't produce a path mkdtemp refuses to create.
|
||||
const safeTag = (tag || '').replace(/[<>:"/\\|?*\s]+/g, '-');
|
||||
const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `pw-${safeTag}-`));
|
||||
dirs.push(dir);
|
||||
return dir;
|
||||
});
|
||||
|
||||
@@ -15,13 +15,17 @@ test.describe('Default JavaScript Sandbox Mode', () => {
|
||||
// Click on sandbox mode selector to open security settings
|
||||
await sandboxLocators.sandboxModeSelector().click();
|
||||
|
||||
// Change to developer mode
|
||||
const developerRadio = sandboxLocators.developerModeRadio();
|
||||
await developerRadio.check();
|
||||
// Safe mode should be selected by default
|
||||
await expect(sandboxLocators.safeModeRadio()).toHaveAttribute('aria-checked', 'true');
|
||||
|
||||
// For developer mode, check if safe mode is currently selected
|
||||
const safeModeChecked = await sandboxLocators.safeModeRadio().isChecked().catch(() => false);
|
||||
await expect(safeModeChecked).toBe(false);
|
||||
// Change to developer mode. The option is a menuitemradio whose checked
|
||||
// state flips only after the async save dispatch resolves
|
||||
const developerRadio = sandboxLocators.developerModeRadio();
|
||||
await developerRadio.click();
|
||||
await expect(developerRadio).toHaveAttribute('aria-checked', 'true');
|
||||
|
||||
// Safe mode should no longer be selected
|
||||
await expect(sandboxLocators.safeModeRadio()).toHaveAttribute('aria-checked', 'false');
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
vars {
|
||||
baseUrl: https://dev.example.com
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
vars {
|
||||
baseUrl: https://api.example.com
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
vars {
|
||||
baseUrl: https://staging.example.com
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import jsyaml from 'js-yaml';
|
||||
import { test, expect } from '../../../playwright';
|
||||
import { test, expect, Page } from '../../../playwright';
|
||||
import { generateCollectionDocs } from '../../utils/page';
|
||||
import { buildCommonLocators } from '../../utils/page/locators';
|
||||
import {
|
||||
getCollectionTreeStructure,
|
||||
waitForCollectionMount,
|
||||
type CollectionTreeItem
|
||||
} from '../../utils/page/mounting';
|
||||
|
||||
@@ -78,6 +77,50 @@ const sidebarItemsToNameTree = (items: CollectionTreeItem[] = []): NameTree[] =>
|
||||
return node;
|
||||
});
|
||||
|
||||
/**
|
||||
* Environments defined in the fixture collection (one `.bru` file each under
|
||||
* `environments/`). All of them should be selected by default in the modal.
|
||||
*/
|
||||
const EXPECTED_ENVIRONMENTS = ['Production', 'Development', 'Staging'];
|
||||
|
||||
/** Extract the full embedded OpenCollection payload from the generated docs HTML. */
|
||||
const parseGeneratedOpenCollection = (html: string): Record<string, any> => {
|
||||
const match = html.match(/const collectionData = ("(?:\\.|[^"\\])*");/);
|
||||
if (!match) {
|
||||
throw new Error('Could not find the embedded collection data in the generated documentation');
|
||||
}
|
||||
const yamlContent = JSON.parse(match[1]) as string;
|
||||
return jsyaml.load(yamlContent) as Record<string, any>;
|
||||
};
|
||||
|
||||
/** Names of the environments embedded in the generated docs (under config.environments). */
|
||||
const generatedEnvironmentNames = (html: string): string[] => {
|
||||
const oc = parseGeneratedOpenCollection(html);
|
||||
const environments = (oc?.config?.environments ?? []) as Array<Record<string, any>>;
|
||||
return environments.map((env) => env?.name);
|
||||
};
|
||||
|
||||
/** Text rendered by the header count, e.g. `(2/3 selected)`. */
|
||||
const selectedCountText = (selected: number): string => `(${selected}/${EXPECTED_ENVIRONMENTS.length} selected)`;
|
||||
|
||||
/**
|
||||
* Open the Generate Documentation modal from the collection context menu and wait until
|
||||
* every fixture environment row has rendered, so selection/count assertions are stable.
|
||||
*/
|
||||
const openDocsModalWithEnvironments = async (page: Page) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
await locators.sidebar.collection(COLLECTION_NAME).hover();
|
||||
await locators.actions.collectionActions(COLLECTION_NAME).click();
|
||||
await locators.generateDocs.menuItem().click();
|
||||
|
||||
const modal = locators.generateDocs.modal();
|
||||
await expect(modal).toBeVisible();
|
||||
await expect(locators.generateDocs.environmentRows()).toHaveCount(EXPECTED_ENVIRONMENTS.length);
|
||||
|
||||
return { locators, modal };
|
||||
};
|
||||
|
||||
test.describe('Generate Documentation', () => {
|
||||
test('orders generated docs to match the sidebar tree (folders by seq, then requests by seq, recursively)', async ({
|
||||
pageWithUserData: page
|
||||
@@ -107,8 +150,6 @@ test.describe('Generate Documentation', () => {
|
||||
}) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
await waitForCollectionMount(page, COLLECTION_NAME);
|
||||
|
||||
await locators.sidebar.collection(COLLECTION_NAME).hover();
|
||||
await locators.actions.collectionActions(COLLECTION_NAME).click();
|
||||
await locators.generateDocs.menuItem().click();
|
||||
@@ -122,4 +163,176 @@ test.describe('Generate Documentation', () => {
|
||||
await locators.generateDocs.cancelButton().click();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('shows the current collection version formatted as a v-prefixed semver', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
await locators.sidebar.collection(COLLECTION_NAME).hover();
|
||||
await locators.actions.collectionActions(COLLECTION_NAME).click();
|
||||
await locators.generateDocs.menuItem().click();
|
||||
|
||||
const modal = locators.generateDocs.modal();
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
// The fixture's bruno.json version ("1") is normalised for display to "v1.0.0".
|
||||
await expect(locators.generateDocs.versionInfo()).toContainText('Collection Version:');
|
||||
await expect(locators.generateDocs.versionValue()).toHaveText('v1.0.0');
|
||||
|
||||
// The fixture has 2 folders (Zoo, Aviary) and 5 requests (Lion, Bear, Parrot,
|
||||
// ReqAlpha, ReqBeta), counted recursively across the whole tree.
|
||||
await expect(locators.generateDocs.versionCounts()).toHaveText('2 Folders • 5 requests');
|
||||
|
||||
await locators.generateDocs.cancelButton().click();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('lists every environment under "Environments to include", all selected by default', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
await locators.sidebar.collection(COLLECTION_NAME).hover();
|
||||
await locators.actions.collectionActions(COLLECTION_NAME).click();
|
||||
await locators.generateDocs.menuItem().click();
|
||||
|
||||
const modal = locators.generateDocs.modal();
|
||||
await expect(modal).toBeVisible();
|
||||
await expect(locators.generateDocs.environmentsTitle()).toBeVisible();
|
||||
|
||||
for (const name of EXPECTED_ENVIRONMENTS) {
|
||||
await expect(locators.generateDocs.environmentRow(name)).toBeVisible();
|
||||
await expect(locators.generateDocs.environmentCheckbox(name)).toBeChecked();
|
||||
}
|
||||
|
||||
// Exactly the fixture's environments are listed — nothing more.
|
||||
await expect(locators.generateDocs.environmentRows()).toHaveCount(EXPECTED_ENVIRONMENTS.length);
|
||||
|
||||
await locators.generateDocs.cancelButton().click();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('includes every environment in the generated docs by default', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
// Ensure all environments have loaded (and stay checked) before generating.
|
||||
const { content } = await generateCollectionDocs(page, COLLECTION_NAME, async () => {
|
||||
for (const name of EXPECTED_ENVIRONMENTS) {
|
||||
await expect(locators.generateDocs.environmentCheckbox(name)).toBeChecked();
|
||||
}
|
||||
});
|
||||
|
||||
expect(generatedEnvironmentNames(content).sort()).toEqual([...EXPECTED_ENVIRONMENTS].sort());
|
||||
});
|
||||
|
||||
test('excludes a deselected environment from the generated docs', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
const { content } = await generateCollectionDocs(page, COLLECTION_NAME, async () => {
|
||||
// Wait for all environments to load, then deselect a single one.
|
||||
for (const name of EXPECTED_ENVIRONMENTS) {
|
||||
await expect(locators.generateDocs.environmentCheckbox(name)).toBeChecked();
|
||||
}
|
||||
await locators.generateDocs.environmentCheckbox('Development').uncheck();
|
||||
await expect(locators.generateDocs.environmentCheckbox('Development')).not.toBeChecked();
|
||||
});
|
||||
|
||||
const envNames = generatedEnvironmentNames(content);
|
||||
expect(envNames).toContain('Production');
|
||||
expect(envNames).toContain('Staging');
|
||||
expect(envNames).not.toContain('Development');
|
||||
});
|
||||
|
||||
test('checks "Select All" and shows a full count when every environment is selected by default', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const { locators, modal } = await openDocsModalWithEnvironments(page);
|
||||
|
||||
await expect(locators.generateDocs.selectAllLabel()).toContainText('Select All');
|
||||
await expect(locators.generateDocs.selectAllCheckbox()).toBeChecked();
|
||||
await expect(locators.generateDocs.selectedCount()).toHaveText(
|
||||
selectedCountText(EXPECTED_ENVIRONMENTS.length)
|
||||
);
|
||||
|
||||
await locators.generateDocs.cancelButton().click();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('shows "Select All" as indeterminate with a partial count when one environment is deselected', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const { locators, modal } = await openDocsModalWithEnvironments(page);
|
||||
|
||||
await locators.generateDocs.environmentCheckbox('Development').uncheck();
|
||||
|
||||
// Some-but-not-all selected -> tri-state checkbox shows the indeterminate state.
|
||||
await expect(locators.generateDocs.selectAllCheckbox()).toBeChecked({ indeterminate: true });
|
||||
await expect(locators.generateDocs.selectedCount()).toHaveText(
|
||||
selectedCountText(EXPECTED_ENVIRONMENTS.length - 1)
|
||||
);
|
||||
|
||||
await locators.generateDocs.cancelButton().click();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('clicking "Select All" deselects every environment, emptying the checkbox and count', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const { locators, modal } = await openDocsModalWithEnvironments(page);
|
||||
await expect(locators.generateDocs.selectAllCheckbox()).toBeChecked();
|
||||
|
||||
await locators.generateDocs.selectAllCheckbox().click();
|
||||
|
||||
await expect(locators.generateDocs.selectAllCheckbox()).not.toBeChecked();
|
||||
await expect(locators.generateDocs.selectedCount()).toHaveText(selectedCountText(0));
|
||||
for (const name of EXPECTED_ENVIRONMENTS) {
|
||||
await expect(locators.generateDocs.environmentCheckbox(name)).not.toBeChecked();
|
||||
}
|
||||
|
||||
await locators.generateDocs.cancelButton().click();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('clicking "Select All" from a partial selection re-selects every environment', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const { locators, modal } = await openDocsModalWithEnvironments(page);
|
||||
|
||||
// Drop into the partial (indeterminate) state first.
|
||||
await locators.generateDocs.environmentCheckbox('Development').uncheck();
|
||||
await expect(locators.generateDocs.selectAllCheckbox()).toBeChecked({ indeterminate: true });
|
||||
|
||||
// Clicking the tri-state checkbox while partial selects everything.
|
||||
await locators.generateDocs.selectAllCheckbox().click();
|
||||
|
||||
await expect(locators.generateDocs.selectAllCheckbox()).toBeChecked();
|
||||
await expect(locators.generateDocs.selectedCount()).toHaveText(
|
||||
selectedCountText(EXPECTED_ENVIRONMENTS.length)
|
||||
);
|
||||
for (const name of EXPECTED_ENVIRONMENTS) {
|
||||
await expect(locators.generateDocs.environmentCheckbox(name)).toBeChecked();
|
||||
}
|
||||
|
||||
await locators.generateDocs.cancelButton().click();
|
||||
await expect(modal).toBeHidden();
|
||||
});
|
||||
|
||||
test('deselecting everything via "Select All" excludes all environments from the generated docs', async ({
|
||||
pageWithUserData: page
|
||||
}) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
const { content } = await generateCollectionDocs(page, COLLECTION_NAME, async () => {
|
||||
await expect(locators.generateDocs.environmentRows()).toHaveCount(EXPECTED_ENVIRONMENTS.length);
|
||||
await locators.generateDocs.selectAllCheckbox().click();
|
||||
await expect(locators.generateDocs.selectedCount()).toHaveText(selectedCountText(0));
|
||||
});
|
||||
|
||||
expect(generatedEnvironmentNames(content)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import { test, expect } from '../../../../playwright';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { readExportedJson } from '../../../utils/helpers';
|
||||
|
||||
// Helper function to load expected fixtures
|
||||
function loadExpectedFixture(fixturePath: string) {
|
||||
const fullPath = path.join(__dirname, '..', '../fixtures', 'environment-exports', fixturePath);
|
||||
@@ -71,7 +73,7 @@ test.describe.serial('Collection Environment Export Tests', () => {
|
||||
expect(fs.existsSync(exportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const exportedContent = JSON.parse(fs.readFileSync(exportedFile, 'utf8'));
|
||||
const exportedContent = await readExportedJson(exportedFile);
|
||||
const expectedContent = loadExpectedFixture('bruno-collection-environments/local.json');
|
||||
|
||||
expect(normalizeExportedContent(exportedContent)).toEqual(expectedContent);
|
||||
@@ -132,7 +134,7 @@ test.describe.serial('Collection Environment Export Tests', () => {
|
||||
expect(fs.existsSync(filePath)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
const content = await readExportedJson(filePath);
|
||||
const expectedContent = loadExpectedFixture(`bruno-collection-environments/${fileName}`);
|
||||
expect(normalizeExportedContent(content)).toEqual(expectedContent);
|
||||
}
|
||||
@@ -196,7 +198,7 @@ test.describe.serial('Collection Environment Export Tests', () => {
|
||||
expect(fs.existsSync(newExportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const exportedContent = JSON.parse(fs.readFileSync(newExportedFile, 'utf8'));
|
||||
const exportedContent = await readExportedJson(newExportedFile);
|
||||
const expectedContent = loadExpectedFixture('bruno-collection-environments/local.json');
|
||||
expect(normalizeExportedContent(exportedContent)).toEqual(expectedContent);
|
||||
});
|
||||
@@ -253,7 +255,7 @@ test.describe.serial('Collection Environment Export Tests', () => {
|
||||
expect(fs.existsSync(exportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const content = JSON.parse(fs.readFileSync(exportedFile, 'utf8'));
|
||||
const content = await readExportedJson(exportedFile);
|
||||
const expectedContent = loadExpectedFixture('local.json');
|
||||
expect(normalizeExportedContent(content)).toEqual(expectedContent);
|
||||
});
|
||||
@@ -302,7 +304,7 @@ test.describe.serial('Collection Environment Export Tests', () => {
|
||||
expect(fs.existsSync(exportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const content = JSON.parse(fs.readFileSync(exportedFile, 'utf8'));
|
||||
const content = await readExportedJson(exportedFile);
|
||||
const expectedContent = loadExpectedFixture('bruno-collection-environments.json');
|
||||
expect(normalizeExportedContent(content)).toEqual(expectedContent);
|
||||
});
|
||||
@@ -364,7 +366,7 @@ test.describe.serial('Collection Environment Export Tests', () => {
|
||||
expect(fs.existsSync(uniqueExportPath)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const exportedContent = JSON.parse(fs.readFileSync(uniqueExportPath, 'utf8'));
|
||||
const exportedContent = await readExportedJson(uniqueExportPath);
|
||||
const expectedContent = loadExpectedFixture('local.json');
|
||||
expect(normalizeExportedContent(exportedContent)).toEqual(expectedContent);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import { test, expect } from '../../../../playwright';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
import { readExportedJson } from '../../../utils/helpers';
|
||||
|
||||
// Helper function to load expected fixtures
|
||||
function loadExpectedFixture(fixturePath: string) {
|
||||
const fullPath = path.join(__dirname, '..', '../fixtures', 'environment-exports', fixturePath);
|
||||
@@ -75,7 +77,7 @@ test.describe.serial('Global Environment Export Tests', () => {
|
||||
expect(fs.existsSync(exportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const exportedContent = JSON.parse(fs.readFileSync(exportedFile, 'utf8'));
|
||||
const exportedContent = await readExportedJson(exportedFile);
|
||||
const expectedContent = loadExpectedFixture('bruno-global-environments/local.json');
|
||||
|
||||
expect(normalizeExportedContent(exportedContent)).toEqual(expectedContent);
|
||||
@@ -136,7 +138,7 @@ test.describe.serial('Global Environment Export Tests', () => {
|
||||
expect(fs.existsSync(filePath)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
const content = await readExportedJson(filePath);
|
||||
const expectedContent = loadExpectedFixture(`bruno-global-environments/${fileName}`);
|
||||
expect(normalizeExportedContent(content)).toEqual(expectedContent);
|
||||
}
|
||||
@@ -200,7 +202,7 @@ test.describe.serial('Global Environment Export Tests', () => {
|
||||
expect(fs.existsSync(newExportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const exportedContent = JSON.parse(fs.readFileSync(newExportedFile, 'utf8'));
|
||||
const exportedContent = await readExportedJson(newExportedFile);
|
||||
const expectedContent = loadExpectedFixture('bruno-global-environments/local.json');
|
||||
expect(normalizeExportedContent(exportedContent)).toEqual(expectedContent);
|
||||
});
|
||||
@@ -259,7 +261,7 @@ test.describe.serial('Global Environment Export Tests', () => {
|
||||
expect(fs.existsSync(exportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const content = JSON.parse(fs.readFileSync(exportedFile, 'utf8'));
|
||||
const content = await readExportedJson(exportedFile);
|
||||
const expectedContent = loadExpectedFixture('local.json');
|
||||
expect(normalizeExportedContent(content)).toEqual(expectedContent);
|
||||
});
|
||||
@@ -308,7 +310,7 @@ test.describe.serial('Global Environment Export Tests', () => {
|
||||
expect(fs.existsSync(exportedFile)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const content = JSON.parse(fs.readFileSync(exportedFile, 'utf8'));
|
||||
const content = await readExportedJson(exportedFile);
|
||||
const expectedContent = loadExpectedFixture('bruno-global-environments.json');
|
||||
expect(normalizeExportedContent(content)).toEqual(expectedContent);
|
||||
});
|
||||
@@ -372,7 +374,7 @@ test.describe.serial('Global Environment Export Tests', () => {
|
||||
expect(fs.existsSync(uniqueExportPath)).toBe(true);
|
||||
|
||||
// Verify file content matches expected fixture
|
||||
const exportedContent = JSON.parse(fs.readFileSync(uniqueExportPath, 'utf8'));
|
||||
const exportedContent = await readExportedJson(uniqueExportPath);
|
||||
const expectedContent = loadExpectedFixture('local.json');
|
||||
expect(normalizeExportedContent(exportedContent)).toEqual(expectedContent);
|
||||
});
|
||||
|
||||
@@ -71,9 +71,9 @@ const closeTabByName = async (page: any, name: string | RegExp) => {
|
||||
const openFolderSettingsTab = async (page: Page, folderName: string) => {
|
||||
await openCollection(page, collectionName);
|
||||
const folderRow = page.locator('.collection-item-name').filter({ hasText: folderName }).first();
|
||||
await expect(folderRow).toBeVisible({ timeout: 5000 });
|
||||
await expect(folderRow).toBeVisible();
|
||||
await folderRow.dblclick();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible({ timeout: 3000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: folderName })).toBeVisible();
|
||||
};
|
||||
|
||||
const reopenClosedTab = async (page: Page, shortcut: () => Promise<void>, expectedTabName: string | RegExp) => {
|
||||
@@ -83,13 +83,13 @@ const reopenClosedTab = async (page: Page, shortcut: () => Promise<void>, expect
|
||||
await shortcut();
|
||||
const reopenedTab = page.locator('.request-tab').filter({ hasText: expectedTabName });
|
||||
if ((await reopenedTab.count()) > 0) {
|
||||
await expect(reopenedTab).toBeVisible({ timeout: 3000 });
|
||||
await expect(reopenedTab).toBeVisible();
|
||||
return;
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible({ timeout: 5000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: expectedTabName })).toBeVisible();
|
||||
};
|
||||
|
||||
const remapKeybinding = async (
|
||||
@@ -99,7 +99,7 @@ const remapKeybinding = async (
|
||||
) => {
|
||||
await openKeybindingsTab(page);
|
||||
const row = page.getByTestId(`keybinding-row-${action}`);
|
||||
await expect(row).toBeVisible({ timeout: 5000 });
|
||||
await expect(row).toBeVisible();
|
||||
await row.scrollIntoViewIfNeeded();
|
||||
await row.hover();
|
||||
const editButton = row.getByTestId(`keybinding-edit-${action}`);
|
||||
@@ -114,7 +114,7 @@ const remapKeybinding = async (
|
||||
}
|
||||
}
|
||||
|
||||
await expect(keybindingInput).toBeVisible({ timeout: 5000 });
|
||||
await expect(keybindingInput).toBeVisible();
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
await pressShortcut();
|
||||
@@ -153,10 +153,10 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const reqTab = page.locator('.request-tab').filter({ hasText: 'req-1' });
|
||||
// Click the tab to guarantee it's the focused/active tab before firing the shortcut.
|
||||
await reqTab.click();
|
||||
await expect(reqTab).toHaveClass(/active/, { timeout: 2000 });
|
||||
await expect(reqTab).toHaveClass(/active/);
|
||||
|
||||
await page.keyboard.press(`${modifier}+KeyW`);
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 });
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('customized Cmd/Ctrl+Shift+X closes the active tab', async ({ page, createTmpDir }) => {
|
||||
@@ -166,7 +166,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await row.hover();
|
||||
await page.getByTestId(`keybinding-edit-closeTab`).click();
|
||||
// Wait for input to enter recording mode
|
||||
await expect(page.getByTestId(`keybinding-input-closeTab`)).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId(`keybinding-input-closeTab`)).toBeVisible();
|
||||
|
||||
// Remove the old keybindings
|
||||
await page.keyboard.down('Backspace');
|
||||
@@ -179,13 +179,13 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await closePreferencesTab(page);
|
||||
|
||||
await openRequest(page, collectionName, 'req-1', { persist: true });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
await page.keyboard.down('KeyX');
|
||||
await page.keyboard.up('KeyX');
|
||||
await page.keyboard.up('Shift');
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 });
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,9 +195,9 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await openRequest(page, collectionName, 'req-2', { persist: true });
|
||||
await openRequest(page, collectionName, 'req-3', { persist: true });
|
||||
await page.getByTestId('runner').click();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible();
|
||||
|
||||
await page.keyboard.down(modifier);
|
||||
await page.keyboard.down('Shift');
|
||||
@@ -205,7 +205,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('KeyW');
|
||||
await page.keyboard.up('Shift');
|
||||
await page.keyboard.up(modifier);
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 });
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('customized Alt+Y closes all tabs', async ({ page }) => {
|
||||
@@ -214,7 +214,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-closeAllTabs');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-row-closeAllTabs').click();
|
||||
await expect(page.getByTestId('keybinding-input-closeAllTabs')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-closeAllTabs')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -228,22 +228,22 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await openRequest(page, collectionName, 'req-1', { persist: true });
|
||||
await openRequest(page, collectionName, 'req-2', { persist: true });
|
||||
await openRequest(page, collectionName, 'req-3', { persist: true });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-1' })).toBeVisible();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-2' })).toBeVisible();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-3' })).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Alt');
|
||||
await page.keyboard.down('KeyY');
|
||||
await page.keyboard.up('KeyY');
|
||||
await page.keyboard.up('Alt');
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2, { timeout: 3000 });
|
||||
await expect(page.locator('.request-tab')).toHaveCount(2);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe.serial('SHORTCUT: Save', () => {
|
||||
test('default Cmd/Ctrl+S save tab', async ({ page, createTmpDir }) => {
|
||||
await page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible();
|
||||
|
||||
// Verify initially there is NO draft indicator (close icon is present)
|
||||
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
|
||||
@@ -284,7 +284,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-save');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-save').click();
|
||||
await expect(page.getByTestId('keybinding-input-save')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-save')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -296,7 +296,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await closePreferencesTab(page);
|
||||
|
||||
await page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible();
|
||||
|
||||
// Verify initially there is NO draft indicator (close icon is present)
|
||||
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
|
||||
@@ -337,7 +337,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
test.describe.serial('SHORTCUT: Save All Tabs', () => {
|
||||
test('default Cmd/Ctrl+Shift+S save all tabs', async ({ page }) => {
|
||||
await page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible();
|
||||
|
||||
// Verify initially there is NO draft indicator (close icon is present)
|
||||
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
|
||||
@@ -409,7 +409,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-saveAllTabs');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-saveAllTabs').click();
|
||||
await expect(page.getByTestId('keybinding-input-saveAllTabs')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-saveAllTabs')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -423,7 +423,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await closePreferencesTab(page);
|
||||
|
||||
await page.getByTestId('collections').locator('.collection-name').filter({ hasText: collectionName }).dblclick();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible();
|
||||
|
||||
// Verify initially there is NO draft indicator (close icon is present)
|
||||
const collectionTab = page.locator('.request-tab').filter({ has: page.locator('.tab-label', { hasText: 'Collection' }) });
|
||||
@@ -500,15 +500,15 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await openRequest(page, collectionName, 'req-4', { persist: true });
|
||||
await openRequest(page, collectionName, 'req-5', { persist: true });
|
||||
await openRequest(page, collectionName, 'req-6', { persist: true });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-6' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-6' })).toBeVisible();
|
||||
|
||||
// req-6 is active (last opened) — press previous → req-5
|
||||
await page.keyboard.press(`${modifier}+Shift+BracketLeft`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/);
|
||||
|
||||
// Press again → req-4
|
||||
await page.keyboard.press(`${modifier}+Shift+BracketLeft`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/);
|
||||
});
|
||||
|
||||
test('customized Shift+P switches to previous tab', async ({ page }) => {
|
||||
@@ -517,7 +517,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-switchToPreviousTab');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-switchToPreviousTab').click();
|
||||
await expect(page.getByTestId('keybinding-input-switchToPreviousTab')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-switchToPreviousTab')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -532,14 +532,14 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await openRequest(page, collectionName, 'req-4', { persist: true });
|
||||
await openRequest(page, collectionName, 'req-5', { persist: true });
|
||||
await openRequest(page, collectionName, 'req-6', { persist: true });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-6' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'req-6' })).toBeVisible();
|
||||
|
||||
// req-6 is active — press Shift+P → req-5
|
||||
await page.keyboard.down('Shift');
|
||||
await page.keyboard.down('KeyP');
|
||||
await page.keyboard.up('KeyP');
|
||||
await page.keyboard.up('Shift');
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -555,11 +555,11 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// req-4 is active — press next → req-5
|
||||
await page.keyboard.press(`${modifier}+Shift+BracketRight`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/);
|
||||
|
||||
// Press again → req-6
|
||||
await page.keyboard.press(`${modifier}+Shift+BracketRight`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/);
|
||||
});
|
||||
|
||||
test('customized Shift+N switches to next tab', async ({ page }) => {
|
||||
@@ -568,7 +568,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-switchToNextTab');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-switchToNextTab').click();
|
||||
await expect(page.getByTestId('keybinding-input-switchToNextTab')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-switchToNextTab')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -592,7 +592,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.down('KeyN');
|
||||
await page.keyboard.up('KeyN');
|
||||
await page.keyboard.up('Shift');
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -609,12 +609,12 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Press Cmd/Ctrl+[ → req-9 moves left, req-8 becomes last
|
||||
await page.keyboard.press(`${modifier}+BracketLeft`);
|
||||
await expect(tabs.nth(totalTabs - 1)).toHaveText(/req-8/, { timeout: 3000 });
|
||||
await expect(tabs.nth(totalTabs - 1)).toHaveText(/req-8/);
|
||||
await expect(tabs.nth(totalTabs - 2)).toHaveText(/req-9/);
|
||||
|
||||
// Press again → req-9 moves one more position left
|
||||
await page.keyboard.press(`${modifier}+BracketLeft`);
|
||||
await expect(tabs.nth(totalTabs - 3)).toHaveText(/req-9/, { timeout: 3000 });
|
||||
await expect(tabs.nth(totalTabs - 3)).toHaveText(/req-9/);
|
||||
});
|
||||
|
||||
test('customized Alt+L moves active tab left', async ({ page }) => {
|
||||
@@ -623,7 +623,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-moveTabLeft');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-moveTabLeft').click();
|
||||
await expect(page.getByTestId('keybinding-input-moveTabLeft')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-moveTabLeft')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -694,7 +694,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-moveTabRight');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-moveTabRight').click();
|
||||
await expect(page.getByTestId('keybinding-input-moveTabRight')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-moveTabRight')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -762,26 +762,26 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await openRequest(page, 'kb-collection', 'req-8', { persist: true });
|
||||
await openRequest(page, 'kb-collection', 'req-9', { persist: true });
|
||||
|
||||
await expect(page.locator('.request-tab')).toHaveCount(9, { timeout: 2000 });
|
||||
await expect(page.locator('.request-tab')).toHaveCount(9);
|
||||
const tabs = page.locator('.request-tab');
|
||||
|
||||
await expect(tabs.nth(0)).toHaveText(/req-1/, { timeout: 2000 });
|
||||
await expect(tabs.nth(0)).toHaveText(/req-1/);
|
||||
await page.keyboard.press(`${modifier}+1`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-1/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-1/);
|
||||
await page.keyboard.press(`${modifier}+2`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-2/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-2/);
|
||||
await page.keyboard.press(`${modifier}+3`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-3/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-3/);
|
||||
await page.keyboard.press(`${modifier}+4`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-4/);
|
||||
await page.keyboard.press(`${modifier}+5`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-5/);
|
||||
await page.keyboard.press(`${modifier}+6`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-6/);
|
||||
await page.keyboard.press(`${modifier}+7`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-7/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-7/);
|
||||
await page.keyboard.press(`${modifier}+8`);
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-8/, { timeout: 3000 });
|
||||
await expect(page.locator('li.request-tab.active')).toHaveText(/req-8/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -802,16 +802,16 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Open Collection-Settings tab (double-click collection name)
|
||||
await page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection' }).dblclick();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible();
|
||||
|
||||
// Open Runner tab
|
||||
await page.getByTestId('runner').click();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Runner' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Runner' })).toBeVisible();
|
||||
|
||||
// Open Variables tab
|
||||
await page.getByTestId('more-actions').click();
|
||||
await page.getByTestId('more-actions-variables').click();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Variables' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Variables' })).toBeVisible();
|
||||
|
||||
// Open Folder-Settings tab (create folder + double-click)
|
||||
await page.locator('.collection-item-name').filter({ hasText: 'kb-folder' }).dblclick();
|
||||
@@ -858,8 +858,8 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
await page.keyboard.press(`${modifier}+KeyF`);
|
||||
|
||||
// await expect(page.getByPlaceholder('Search requests...')).toBeVisible({ timeout: 3000 });
|
||||
await expect(page.getByTestId('sidebar-search-input')).toBeVisible({ timeout: 3000 });
|
||||
// await expect(page.getByPlaceholder('Search requests...')).toBeVisible();
|
||||
await expect(page.getByTestId('sidebar-search-input')).toBeVisible();
|
||||
await page.getByTitle('Search requests').click();
|
||||
});
|
||||
|
||||
@@ -869,7 +869,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-sidebarSearch');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-sidebarSearch').click();
|
||||
await expect(page.getByTestId('keybinding-input-sidebarSearch')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-sidebarSearch')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -884,7 +884,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('KeyF');
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
await expect(page.getByTestId('sidebar-search-input')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('sidebar-search-input')).toBeVisible();
|
||||
await page.getByTitle('Search requests').click();
|
||||
});
|
||||
});
|
||||
@@ -905,7 +905,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.type('https://echo.usebruno.com');
|
||||
await page.getByTestId('create-new-request-button').click();
|
||||
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'nr-folder' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'nr-folder' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Alt+N open new request modal', async ({ page, createTmpDir }) => {
|
||||
@@ -914,7 +914,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-newRequest');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-newRequest').click();
|
||||
await expect(page.getByTestId('keybinding-input-newRequest')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-newRequest')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -935,7 +935,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.type('https://echo.usebruno.com');
|
||||
await page.getByTestId('create-new-request-button').click();
|
||||
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'nr-collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'nr-collection' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -952,7 +952,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify rename modal opens
|
||||
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i });
|
||||
await expect(renameModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(renameModal).toBeVisible();
|
||||
|
||||
// Fill in the rename req name
|
||||
const requestNameInput = page.locator('#collection-item-name');
|
||||
@@ -962,8 +962,8 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('rename-item-button').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
// await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1-rename' })).toBeVisible({ timeout: 2000 });
|
||||
// await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1' })).toBeVisible();
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1-rename' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('default Cmd/Ctrl+R open rename item modal for folder', async ({ page, createTmpDir }) => {
|
||||
@@ -977,7 +977,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify rename modal opens
|
||||
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i });
|
||||
await expect(renameModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(renameModal).toBeVisible();
|
||||
|
||||
// Fill in the rename req name
|
||||
const folderNameInput = page.locator('#collection-item-name');
|
||||
@@ -987,7 +987,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('rename-item-button').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-renamed' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-renamed' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('default Cmd/Ctrl+R open rename item modal for collection', async ({ page, createTmpDir }) => {
|
||||
@@ -1001,7 +1001,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify rename modal opens
|
||||
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i });
|
||||
await expect(renameModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(renameModal).toBeVisible();
|
||||
|
||||
// Fill in the rename req name
|
||||
const collectionInput = page.locator('#collection-name');
|
||||
@@ -1011,7 +1011,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.locator('.submit').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
await expect(page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection-renamed' })).toBeVisible({ timeout: 3000 });
|
||||
await expect(page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection-renamed' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Alt+X open rename item modal for request', async ({ page, createTmpDir }) => {
|
||||
@@ -1025,7 +1025,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-renameItem');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-renameItem').click();
|
||||
await expect(page.getByTestId('keybinding-input-renameItem')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-renameItem')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1042,7 +1042,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify rename modal opens
|
||||
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename request/i });
|
||||
await expect(renameModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(renameModal).toBeVisible();
|
||||
|
||||
// Fill in the rename req name
|
||||
const requestNameInput = page.locator('#collection-item-name');
|
||||
@@ -1052,7 +1052,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('rename-item-button').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1-renamed-altx' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1-renamed-altx' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Alt+R open rename item modal for folder', async ({ page, createTmpDir }) => {
|
||||
@@ -1074,7 +1074,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify rename modal opens
|
||||
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename folder/i });
|
||||
await expect(renameModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(renameModal).toBeVisible();
|
||||
|
||||
// Fill in the rename req name
|
||||
const folderNameInput = page.locator('#collection-item-name');
|
||||
@@ -1084,7 +1084,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('rename-item-button').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-renamed-altx-src' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-renamed-altx-src' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Alt+R open rename item modal for collection', async ({ page, createTmpDir }) => {
|
||||
@@ -1105,7 +1105,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify rename modal opens
|
||||
const renameModal = page.locator('.bruno-modal-card').filter({ hasText: /rename collection/i });
|
||||
await expect(renameModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(renameModal).toBeVisible();
|
||||
|
||||
// Fill in the rename req name
|
||||
const collectionInput = page.locator('#collection-name');
|
||||
@@ -1115,7 +1115,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.locator('.submit').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
await expect(page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection-renamed-altx' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection-renamed-altx' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1131,7 +1131,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify clone modal opens
|
||||
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i });
|
||||
await expect(cloneModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(cloneModal).toBeVisible();
|
||||
|
||||
// Fill in the clone req name
|
||||
const requestNameInput = page.locator('#collection-item-name');
|
||||
@@ -1141,7 +1141,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('clone-item-button').click();
|
||||
|
||||
// Verify cloned request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1 clone 1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-1 clone 1' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('default Cmd/Ctrl+D open clone item modal for folder', async ({ page, createTmpDir }) => {
|
||||
@@ -1155,7 +1155,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify clone modal opens
|
||||
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i });
|
||||
await expect(cloneModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(cloneModal).toBeVisible();
|
||||
|
||||
// Fill in the clone kb-folder name
|
||||
const folderNameInput = page.locator('#collection-item-name');
|
||||
@@ -1165,7 +1165,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('clone-item-button').click();
|
||||
|
||||
// Verify cloned request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder clone 1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder clone 1' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Alt+D open clone item modal for request', async ({ page, createTmpDir }) => {
|
||||
@@ -1179,7 +1179,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-cloneItem');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-cloneItem').click();
|
||||
await expect(page.getByTestId('keybinding-input-cloneItem')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-cloneItem')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1197,7 +1197,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify clone modal opens
|
||||
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone request/i });
|
||||
await expect(cloneModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(cloneModal).toBeVisible();
|
||||
|
||||
// Fill in the clone req name
|
||||
const requestNameInput = page.locator('#collection-item-name');
|
||||
@@ -1207,7 +1207,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('clone-item-button').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-2 clone 1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-2 clone 1' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Alt+D open clone item modal for folder', async ({ page, createTmpDir }) => {
|
||||
@@ -1226,7 +1226,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify clone modal opens
|
||||
const cloneModal = page.locator('.bruno-modal-card').filter({ hasText: /clone folder/i });
|
||||
await expect(cloneModal).toBeVisible({ timeout: 3000 });
|
||||
await expect(cloneModal).toBeVisible();
|
||||
|
||||
// Fill in the clone req name
|
||||
const folderNameInput = page.locator('#collection-item-name');
|
||||
@@ -1236,7 +1236,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.getByTestId('clone-item-button').click();
|
||||
|
||||
// Verify renamed request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-clone-src copy 1' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'kb-folder-clone-src copy 1' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1252,7 +1252,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.press(`${modifier}+KeyV`);
|
||||
|
||||
// Verify cloned request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-3 (1)' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-3 (1)' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('default Cmd/Ctrl+C/V copy paste item for folder', async ({ page }) => {
|
||||
@@ -1280,7 +1280,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-copyItem');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-copyItem').click();
|
||||
await expect(page.getByTestId('keybinding-input-copyItem')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-copyItem')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1294,7 +1294,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row2 = page.getByTestId('keybinding-row-pasteItem');
|
||||
await row2.hover();
|
||||
await page.getByTestId('keybinding-edit-pasteItem').click();
|
||||
await expect(page.getByTestId('keybinding-input-pasteItem')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-pasteItem')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1315,7 +1315,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
// Verify cloned request appears in sidebar
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-4 (1)' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'req-4 (1)' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Alt+C/V copy paste item for folder', async ({ page, createTmpDir }) => {
|
||||
@@ -1357,16 +1357,14 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.press(`${modifier}+Backslash`);
|
||||
|
||||
await expect.poll(
|
||||
() => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width)),
|
||||
{ timeout: 5000 }
|
||||
() => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width))
|
||||
).toBeLessThan(5);
|
||||
|
||||
// Press Cmd/Ctrl+\ to collapse expanded sidebar
|
||||
await page.keyboard.press(`${modifier}+Backslash`);
|
||||
|
||||
await expect.poll(
|
||||
() => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width)),
|
||||
{ timeout: 5000 }
|
||||
() => page.locator('aside.sidebar').evaluate((el) => parseFloat(getComputedStyle(el).width))
|
||||
).toBeGreaterThan(200);
|
||||
});
|
||||
|
||||
@@ -1376,7 +1374,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-collapseSidebar');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-collapseSidebar').click();
|
||||
await expect(page.getByTestId('keybinding-input-collapseSidebar')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-collapseSidebar')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1395,8 +1393,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify sidebar collapsed to 0px
|
||||
await expect.poll(
|
||||
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width),
|
||||
{ timeout: 5000 }
|
||||
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width)
|
||||
).toBe('0px');
|
||||
|
||||
// Trigger the remapped shortcut to expand sidebar
|
||||
@@ -1406,8 +1403,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('Shift');
|
||||
|
||||
await expect.poll(
|
||||
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width),
|
||||
{ timeout: 5000 }
|
||||
() => page.locator('aside.sidebar').evaluate((el) => getComputedStyle(el).width)
|
||||
).toBe('250px');
|
||||
});
|
||||
});
|
||||
@@ -1418,14 +1414,14 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
test('default Cmd/Ctrl+T opens terminal', async ({ page, createTmpDir }) => {
|
||||
// Open Collection-Settings tab (double-click collection name)
|
||||
await page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection' }).click();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible();
|
||||
|
||||
// Press Cmd/Ctrl+T to open terminal at workspace level
|
||||
await page.keyboard.press(`${modifier}+KeyT`);
|
||||
|
||||
// Verify terminal session is visible using data-testid
|
||||
const collectionTerminalSession = page.getByTestId('session-list-0');
|
||||
await expect(collectionTerminalSession).toBeVisible({ timeout: 2000 });
|
||||
await expect(collectionTerminalSession).toBeVisible();
|
||||
|
||||
const collectionSession = collectionTerminalSession;
|
||||
await expect(collectionSession).toContainText('kb-collection');
|
||||
@@ -1434,11 +1430,11 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
// Open Folder-Settings tab (create folder + double-click)
|
||||
// Open folder settings
|
||||
await page.locator('.collection-item-name').filter({ hasText: 'kb-terminal-folder' }).dblclick();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'kb-terminal-folder' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'kb-terminal-folder' })).toBeVisible();
|
||||
|
||||
await page.keyboard.press(`${modifier}+KeyT`);
|
||||
const folderTerminalSession = page.getByTestId('session-list-1');
|
||||
await expect(folderTerminalSession).toBeVisible({ timeout: 2000 });
|
||||
await expect(folderTerminalSession).toBeVisible();
|
||||
|
||||
// Verify the terminal session name is the workspace name (default_workspace)
|
||||
const folderSessionName = folderTerminalSession;
|
||||
@@ -1458,7 +1454,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-openTerminal');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-openTerminal').click();
|
||||
await expect(page.getByTestId('keybinding-input-openTerminal')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-openTerminal')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1468,7 +1464,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
await page.getByTestId('collections').locator('.collection-name').filter({ hasText: 'kb-collection' }).click();
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'collection' })).toBeVisible();
|
||||
|
||||
// Press Cmd/Ctrl+T to open terminal at workspace level
|
||||
await page.keyboard.down('Alt');
|
||||
@@ -1479,7 +1475,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
// Verify terminal session is visible using data-testid
|
||||
const collectionTerminalSession = page.getByTestId('session-list-0');
|
||||
await expect(collectionTerminalSession).toBeVisible({ timeout: 2000 });
|
||||
await expect(collectionTerminalSession).toBeVisible();
|
||||
|
||||
const collectionSession = collectionTerminalSession;
|
||||
await expect(collectionSession).toContainText('kb-collection');
|
||||
@@ -1492,7 +1488,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('KeyT');
|
||||
await page.keyboard.up('Alt');
|
||||
const folderTerminalSession = page.getByTestId('session-list-1');
|
||||
await expect(folderTerminalSession).toBeVisible({ timeout: 2000 });
|
||||
await expect(folderTerminalSession).toBeVisible();
|
||||
|
||||
// Verify the terminal session name is the workspace name (default_workspace)
|
||||
const folderSessionName = folderTerminalSession;
|
||||
@@ -1518,21 +1514,21 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
await expect(
|
||||
page.getByTestId('response-layout-toggle-btn')
|
||||
).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 });
|
||||
).toHaveAttribute('title', 'Switch to horizontal layout');
|
||||
|
||||
// Press Cmd/Ctrl+J to change layout
|
||||
await page.keyboard.press(`${modifier}+KeyJ`);
|
||||
|
||||
await expect(
|
||||
page.getByTestId('response-layout-toggle-btn')
|
||||
).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 });
|
||||
).toHaveAttribute('title', 'Switch to vertical layout');
|
||||
|
||||
// Press Cmd/Ctrl+J to change layout
|
||||
await page.keyboard.press(`${modifier}+KeyJ`);
|
||||
|
||||
await expect(
|
||||
page.getByTestId('response-layout-toggle-btn')
|
||||
).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 });
|
||||
).toHaveAttribute('title', 'Switch to horizontal layout');
|
||||
});
|
||||
|
||||
test('customized Alt+Shift+Y change layout orientation', async ({ page, createTmpDir }) => {
|
||||
@@ -1546,7 +1542,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-changeLayout');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-changeLayout').click();
|
||||
await expect(page.getByTestId('keybinding-input-changeLayout')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-changeLayout')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1568,7 +1564,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
await expect(
|
||||
page.getByTestId('response-layout-toggle-btn')
|
||||
).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 });
|
||||
).toHaveAttribute('title', 'Switch to vertical layout');
|
||||
|
||||
// Press Cmd/Ctrl+J to change layout
|
||||
await page.keyboard.down('Alt');
|
||||
@@ -1580,7 +1576,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
await expect(
|
||||
page.getByTestId('response-layout-toggle-btn')
|
||||
).toHaveAttribute('title', 'Switch to horizontal layout', { timeout: 2000 });
|
||||
).toHaveAttribute('title', 'Switch to horizontal layout');
|
||||
|
||||
// Press Cmd/Ctrl+J to change layout
|
||||
await page.keyboard.down('Alt');
|
||||
@@ -1592,7 +1588,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
|
||||
await expect(
|
||||
page.getByTestId('response-layout-toggle-btn')
|
||||
).toHaveAttribute('title', 'Switch to vertical layout', { timeout: 2000 });
|
||||
).toHaveAttribute('title', 'Switch to vertical layout');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1609,7 +1605,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('Comma');
|
||||
await page.keyboard.up(modifier);
|
||||
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible({ timeout: 3000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('customized Cmd/Ctrl+P open preferences', async ({ page }) => {
|
||||
@@ -1618,7 +1614,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-openPreferences');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-openPreferences').click();
|
||||
await expect(page.getByTestId('keybinding-input-openPreferences')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-openPreferences')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1638,7 +1634,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('KeyP');
|
||||
await page.keyboard.up(modifier);
|
||||
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible({ timeout: 3000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Preferences' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1655,7 +1651,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up(modifier);
|
||||
|
||||
await page.getByTestId('global-search-input').click();
|
||||
await expect(page.getByTestId('global-search-input')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('global-search-input')).toBeVisible();
|
||||
|
||||
// await page.waitForTimeout(500);
|
||||
await page.keyboard.down('Escape');
|
||||
@@ -1668,7 +1664,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-globalSearch');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-globalSearch').click();
|
||||
await expect(page.getByTestId('keybinding-input-globalSearch')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-globalSearch')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1683,7 +1679,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
await page.getByTestId('global-search-input').click();
|
||||
await expect(page.getByTestId('global-search-input')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('global-search-input')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Escape');
|
||||
await page.keyboard.up('Escape');
|
||||
@@ -1705,7 +1701,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('KeyE');
|
||||
await page.keyboard.up(modifier);
|
||||
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('open environment tab of collection customized Alt+E', async ({ page, createTmpDir }) => {
|
||||
@@ -1719,7 +1715,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-editEnvironment');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-editEnvironment').click();
|
||||
await expect(page.getByTestId('keybinding-input-editEnvironment')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-editEnvironment')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1735,7 +1731,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
await page.keyboard.up('KeyE');
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.locator('.request-tab').filter({ hasText: 'Environments' })).toBeVisible();
|
||||
|
||||
// Rest Default - just in case to not fail shortcuts in other places
|
||||
await openKeybindingsTab(page);
|
||||
@@ -1768,7 +1764,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
|
||||
await bodyEditor.click();
|
||||
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"', { timeout: 5000 });
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
|
||||
|
||||
// Cursor is still in the body CodeMirror — press Cmd/Ctrl+Enter to send
|
||||
await page.keyboard.press(`${modifier}+Enter`);
|
||||
@@ -1797,7 +1793,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
|
||||
await bodyEditor.click();
|
||||
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"', { timeout: 5000 });
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
|
||||
|
||||
// First send to populate response
|
||||
await page.keyboard.press(`${modifier}+Enter`);
|
||||
@@ -1867,7 +1863,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-sendRequest');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-sendRequest').click();
|
||||
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1892,7 +1888,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
|
||||
await bodyEditor.click();
|
||||
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"', { timeout: 5000 });
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
|
||||
|
||||
// Cursor is still in the body CodeMirror — press Shift+Enter (customized) to send
|
||||
await page.keyboard.press('Shift+Enter');
|
||||
@@ -1916,7 +1912,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-sendRequest');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-sendRequest').click();
|
||||
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
@@ -1939,7 +1935,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const bodyEditor = page.getByTestId('request-body-editor').locator('.CodeMirror');
|
||||
await bodyEditor.click();
|
||||
await page.keyboard.type('{"name": "Bruno", "version": 2, "tags": ["api", "client", "http"], "active": true, "meta": {"author": "user", "created": "2025-01-01", "updated": "2025-06-01"}, "counts": {"requests": 42, "collections": 7}}');
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"', { timeout: 5000 });
|
||||
await expect(page.getByTestId('request-body-editor')).toContainText('"name": "Bruno"');
|
||||
|
||||
// First send with Shift+Enter to populate response
|
||||
await page.keyboard.press('Shift+Enter');
|
||||
@@ -1972,7 +1968,7 @@ test.describe('Shortcut Keys - BOUND_ACTIONS', () => {
|
||||
const row = page.getByTestId('keybinding-row-sendRequest');
|
||||
await row.hover();
|
||||
await page.getByTestId('keybinding-edit-sendRequest').click();
|
||||
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible({ timeout: 2000 });
|
||||
await expect(page.getByTestId('keybinding-input-sendRequest')).toBeVisible();
|
||||
|
||||
await page.keyboard.down('Backspace');
|
||||
|
||||
|
||||
@@ -265,6 +265,12 @@ test.describe('Snapshot: Workspace State', () => {
|
||||
});
|
||||
|
||||
test('workspace collection sorting persists across workspace switches and restart', async ({ launchElectronApp, createTmpDir }) => {
|
||||
// Heaviest restart test in this file (4 collections across 2 workspaces, sort
|
||||
// toggles, multiple snapshot-file polls, then a full restart). It needs the same
|
||||
// extended budget as the other multi-collection restart tests, otherwise it runs
|
||||
// out of time mid-restart on slower runners (e.g. Windows CI, especially when the
|
||||
// job doesn't set CI=true and the default collapses to 30s).
|
||||
test.setTimeout(60000);
|
||||
const userDataPath = await createTmpDir('snap-ws-collection-sorting');
|
||||
|
||||
const defaultColZPath = await createTmpDir('default-col-zulu');
|
||||
|
||||
@@ -58,7 +58,9 @@ test.describe('Snapshot: Collection Environment Persistence', () => {
|
||||
expect(Array.isArray(snapshot?.collections)).toBe(true);
|
||||
|
||||
const migratedCollectionEntry = snapshot?.collections?.find(
|
||||
(collection: any) => collection?.pathname === migrationCollectionPath
|
||||
(collection: any) =>
|
||||
typeof collection?.pathname === 'string'
|
||||
&& path.normalize(collection.pathname) === path.normalize(migrationCollectionPath)
|
||||
);
|
||||
expect(migratedCollectionEntry).toBeTruthy();
|
||||
console.log(JSON.stringify(migratedCollectionEntry));
|
||||
@@ -128,6 +130,10 @@ test.describe('Snapshot: Collection Environment Persistence', () => {
|
||||
});
|
||||
|
||||
test('keeps selected environments for three collections across delayed switches and snapshot updates', async ({ launchElectronApp, createTmpDir }) => {
|
||||
// Heavy test: 3 collections, several 2s settle waits, plus a full restart. The default
|
||||
// budget collapses to 30s when the runner doesn't set CI=true, which isn't enough on
|
||||
// slower machines (e.g. Windows) and times out mid-restart. Give it room.
|
||||
test.setTimeout(60000);
|
||||
const userDataPath = await createTmpDir('snap-env-persistence-three');
|
||||
const firstCollectionPath = await createTmpDir('snap-col-a-three');
|
||||
const secondCollectionPath = await createTmpDir('snap-col-b-three');
|
||||
|
||||
25
tests/utils/helpers.ts
Normal file
25
tests/utils/helpers.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import fs from 'fs';
|
||||
|
||||
/**
|
||||
* Read and JSON-parse a file, retrying until it has parseable content.
|
||||
*
|
||||
* Reading an exported file immediately after the app writes it can observe a
|
||||
* created-but-not-yet-flushed (empty) file — most often on Windows — which makes
|
||||
* JSON.parse throw "Unexpected end of JSON input". Poll until the file has content
|
||||
* that parses as JSON before returning it.
|
||||
*/
|
||||
export async function readExportedJson(filePath: string, { tries = 50, interval = 100 } = {}) {
|
||||
let lastError: unknown;
|
||||
for (let attempt = 0; attempt < tries; attempt++) {
|
||||
try {
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
if (raw.trim().length > 0) {
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
throw lastError ?? new Error(`Timed out waiting for valid JSON at ${filePath}`);
|
||||
}
|
||||
@@ -1869,7 +1869,8 @@ const openWorkspaceFromDialog = async (app: any, page: any, targetPath: string)
|
||||
*/
|
||||
const generateCollectionDocs = async (
|
||||
page: Page,
|
||||
collectionName: string
|
||||
collectionName: string,
|
||||
beforeGenerate?: () => Promise<void>
|
||||
): Promise<{ content: string; fileName: string }> => {
|
||||
return await test.step(`Generate docs for collection "${collectionName}"`, async () => {
|
||||
const locators = buildCommonLocators(page);
|
||||
@@ -1894,6 +1895,12 @@ const generateCollectionDocs = async (
|
||||
const generateButton = locators.generateDocs.generateButton();
|
||||
await expect(generateButton).toBeEnabled({ timeout: 10000 });
|
||||
|
||||
// Let the caller interact with the modal (e.g. toggle environment selection)
|
||||
// after it is ready and before the docs are generated.
|
||||
if (beforeGenerate) {
|
||||
await beforeGenerate();
|
||||
}
|
||||
|
||||
// Arm the renderer-side interception before the save fires. `file-saver`
|
||||
// (v2) reads the Blob through `URL.createObjectURL` and then triggers the
|
||||
// save by dispatching a synthetic click on a detached `<a download>` (via
|
||||
|
||||
@@ -157,7 +157,27 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
}),
|
||||
heading: () => page.locator('.bruno-modal').getByText('Interactive API Documentation'),
|
||||
generateButton: () => page.locator('.bruno-modal').getByRole('button', { name: 'Generate', exact: true }),
|
||||
cancelButton: () => page.locator('.bruno-modal').getByRole('button', { name: 'Cancel', exact: true })
|
||||
cancelButton: () => page.locator('.bruno-modal').getByRole('button', { name: 'Cancel', exact: true }),
|
||||
// Collection version (read-only) display
|
||||
versionInfo: () => page.locator('.bruno-modal').getByTestId('version-info'),
|
||||
versionValue: () => page.locator('.bruno-modal').getByTestId('version-value'),
|
||||
versionCounts: () => page.locator('.bruno-modal').getByTestId('version-summary'),
|
||||
// Environment selection list
|
||||
environmentsTitle: () => page.locator('.bruno-modal').getByTestId('env-section-title'),
|
||||
// Header controls: tri-state "select all" checkbox + "X/Y selected" count
|
||||
selectAllCheckbox: () => page.locator('.bruno-modal').getByTestId('env-select-all'),
|
||||
selectAllLabel: () => page.locator('.bruno-modal').getByTestId('env-select-all-label'),
|
||||
selectedCount: () => page.locator('.bruno-modal').getByTestId('env-selected-count'),
|
||||
environmentRows: () => page.locator('.bruno-modal').getByTestId('env-row'),
|
||||
environmentRow: (name: string) =>
|
||||
page.locator('.bruno-modal').getByTestId('env-row').filter({ has: page.getByText(name, { exact: true }) }),
|
||||
// A row has exactly one checkbox; its data-testid is uid-keyed, so select it by role within the named row.
|
||||
environmentCheckbox: (name: string) =>
|
||||
page
|
||||
.locator('.bruno-modal')
|
||||
.getByTestId('env-row')
|
||||
.filter({ has: page.getByText(name, { exact: true }) })
|
||||
.getByRole('checkbox')
|
||||
},
|
||||
runnerResults: {
|
||||
itemPath: (name: string) => page.getByTestId('runner-result-item').filter({ hasText: name })
|
||||
|
||||
Reference in New Issue
Block a user