@verdaccio/e2e-ui 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/commands/index.cjs +7 -0
- package/build/cjs/commands/index.cjs.map +1 -1
- package/build/cjs/features.cjs +73 -0
- package/build/cjs/features.cjs.map +1 -0
- package/build/cjs/index.cjs +8 -1
- package/build/cjs/index.cjs.map +1 -1
- package/build/cjs/testIds.cjs +16 -1
- package/build/cjs/testIds.cjs.map +1 -1
- package/build/cjs/tests/change-password.cjs +133 -0
- package/build/cjs/tests/change-password.cjs.map +1 -0
- package/build/cjs/tests/home.cjs +3 -1
- package/build/cjs/tests/home.cjs.map +1 -1
- package/build/cjs/tests/layout.cjs +10 -0
- package/build/cjs/tests/layout.cjs.map +1 -1
- package/build/cjs/tests/publish.cjs +25 -2
- package/build/cjs/tests/publish.cjs.map +1 -1
- package/build/cjs/tests/search.cjs +48 -0
- package/build/cjs/tests/search.cjs.map +1 -1
- package/build/cjs/tests/settings.cjs +3 -1
- package/build/cjs/tests/settings.cjs.map +1 -1
- package/build/cjs/tests/signin.cjs +25 -3
- package/build/cjs/tests/signin.cjs.map +1 -1
- package/build/commands/index.d.ts +11 -0
- package/build/esm/commands/index.js +7 -0
- package/build/esm/commands/index.js.map +1 -1
- package/build/esm/features.js +71 -0
- package/build/esm/features.js.map +1 -0
- package/build/esm/index.js +6 -2
- package/build/esm/index.js.map +1 -1
- package/build/esm/testIds.js +16 -1
- package/build/esm/testIds.js.map +1 -1
- package/build/esm/tests/change-password.js +133 -0
- package/build/esm/tests/change-password.js.map +1 -0
- package/build/esm/tests/home.js +3 -1
- package/build/esm/tests/home.js.map +1 -1
- package/build/esm/tests/layout.js +10 -0
- package/build/esm/tests/layout.js.map +1 -1
- package/build/esm/tests/publish.js +25 -2
- package/build/esm/tests/publish.js.map +1 -1
- package/build/esm/tests/search.js +48 -0
- package/build/esm/tests/search.js.map +1 -1
- package/build/esm/tests/settings.js +3 -1
- package/build/esm/tests/settings.js.map +1 -1
- package/build/esm/tests/signin.js +25 -3
- package/build/esm/tests/signin.js.map +1 -1
- package/build/features.d.ts +129 -0
- package/build/index.d.ts +3 -1
- package/build/testIds.d.ts +28 -0
- package/build/tests/change-password.d.ts +29 -0
- package/build/tests/index.d.ts +1 -0
- package/build/types.d.ts +8 -0
- package/package.json +1 -1
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
1
2
|
//#region src/tests/publish.ts
|
|
2
3
|
function publishTests(config) {
|
|
3
4
|
const { header, package: pkg } = config.testIds;
|
|
4
5
|
const { markdownBody, loginDialog } = config.selectors;
|
|
6
|
+
const { features } = config;
|
|
5
7
|
describe("publish", () => {
|
|
6
8
|
const pkgName = "@verdaccio/pkg-scoped";
|
|
9
|
+
const depName = "debug";
|
|
10
|
+
const depVersion = "4.0.0";
|
|
7
11
|
let tempFolder = null;
|
|
8
12
|
/**
|
|
9
13
|
* Log in once, reuse the session across every test in this suite.
|
|
@@ -36,7 +40,7 @@ function publishTests(config) {
|
|
|
36
40
|
cy.task("publishPackage", {
|
|
37
41
|
pkgName,
|
|
38
42
|
version: "1.0.0",
|
|
39
|
-
dependencies: {
|
|
43
|
+
dependencies: { [depName]: depVersion },
|
|
40
44
|
unique: true
|
|
41
45
|
}).then((result) => {
|
|
42
46
|
tempFolder = result?.tempFolder ?? null;
|
|
@@ -101,7 +105,8 @@ function publishTests(config) {
|
|
|
101
105
|
cy.getByTestId(pkg.dependenciesTab).click();
|
|
102
106
|
cy.wait(100);
|
|
103
107
|
cy.getByTestId(pkg.dependencies).should("have.length", 1);
|
|
104
|
-
cy.getByTestId(
|
|
108
|
+
cy.getByTestId(depName).should("be.visible").and("contain.text", depName).and("contain.text", depVersion);
|
|
109
|
+
cy.getByTestId(depName).invoke("text").should("match", new RegExp(`${depName}\\s*:\\s*${depVersion}`));
|
|
105
110
|
});
|
|
106
111
|
it("should click on versions tab", () => {
|
|
107
112
|
cy.wait("@pkgs");
|
|
@@ -121,6 +126,24 @@ function publishTests(config) {
|
|
|
121
126
|
cy.getByTestId(pkg.uplinksTab).click();
|
|
122
127
|
cy.getByTestId(pkg.noUplinks).should("be.visible");
|
|
123
128
|
});
|
|
129
|
+
require_features.maybeIt(features.publish.downloadTarball)("should fetch the tarball when the download button is clicked", () => {
|
|
130
|
+
cy.intercept("GET", "**/pkg-scoped-*.tgz").as("tarballFetch");
|
|
131
|
+
cy.wait("@pkgs");
|
|
132
|
+
cy.getByTestId(pkg.title).first().click();
|
|
133
|
+
cy.wait("@sidebar");
|
|
134
|
+
cy.getByTestId(pkg.downloadTarballBtn).should("be.visible").click();
|
|
135
|
+
cy.wait("@tarballFetch", { timeout: 1e4 }).its("response.statusCode").should("eq", 200);
|
|
136
|
+
});
|
|
137
|
+
require_features.maybeIt(features.publish.rawViewer)("should open the raw manifest viewer when the raw button is clicked", () => {
|
|
138
|
+
cy.wait("@pkgs");
|
|
139
|
+
cy.getByTestId(pkg.title).first().click();
|
|
140
|
+
cy.wait("@sidebar");
|
|
141
|
+
cy.getByTestId(pkg.rawBtn).should("be.visible").click();
|
|
142
|
+
cy.getByTestId(pkg.rawViewerDialog, { timeout: 5e3 }).should("be.visible");
|
|
143
|
+
cy.getByTestId(pkg.rawViewerDialog).should("contain.text", pkgName);
|
|
144
|
+
cy.getByTestId(pkg.closeRawViewer).click();
|
|
145
|
+
cy.getByTestId(pkg.rawViewerDialog).should("not.exist");
|
|
146
|
+
});
|
|
124
147
|
});
|
|
125
148
|
}
|
|
126
149
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish.cjs","names":[],"sources":["../../../src/tests/publish.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { RegistryConfig } from '../types';\n\nexport function publishTests(config: RegistryConfig) {\n const { header, package: pkg } = config.testIds;\n const { markdownBody, loginDialog } = config.selectors;\n\n describe('publish', () => {\n const pkgName = '@verdaccio/pkg-scoped';\n // Per-test state so afterEach can clean up the specific publish\n // that this test created (temp folder + registry entry).\n let tempFolder: string | null = null;\n\n /**\n * Log in once, reuse the session across every test in this suite.\n * `cy.session` caches cookies + localStorage keyed on the first\n * argument, so subsequent calls restore without hitting the network.\n */\n const loginOnce = () => {\n cy.session(\n ['publish-suite', config.credentials.user],\n () => {\n cy.visit(config.registryUrl);\n cy.login(config.credentials.user, config.credentials.password, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@sign');\n },\n {\n validate() {\n cy.request({\n url: `${config.registryUrl}/-/verdaccio/data/packages`,\n failOnStatusCode: false,\n })\n .its('status')\n .should('be.oneOf', [200, 304]);\n },\n cacheAcrossSpecs: true,\n }\n );\n };\n\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n cy.intercept('GET', '/-/verdaccio/data/packages').as('pkgs');\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}`).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}`).as('readme');\n\n // Publish a fresh copy for this test. `unique: true` appends a\n // timestamp suffix so the version is distinct per test even when\n // the registry briefly has a stale copy from a prior afterEach.\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n dependencies: { debug: '4.0.0' },\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n\n loginOnce();\n cy.visit(config.registryUrl);\n });\n\n afterEach(() => {\n // Remove the package from the registry AND the local temp folder.\n // Run both regardless of test outcome so the next test starts\n // clean. `unpublishPackage` treats 404 as success, so a re-run\n // after a half-published state is still safe.\n cy.task('unpublishPackage', { pkgName, tempFolder: tempFolder ?? undefined });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n it('should have one published package', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).should('have.length.at.least', 1);\n });\n\n it('should navigate to page detail', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n });\n\n it('should have readme content', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.readme).should('be.visible');\n cy.get(markdownBody).should('have.length', 1);\n // publishPackage writes a README whose body contains \"e2e testing\".\n cy.contains(markdownBody, /test/);\n cy.contains(`${markdownBody} h1`, pkgName).should('be.visible');\n });\n\n it('should render the sidebar with install commands for npm, yarn, pnpm', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n cy.getByTestId(pkg.sidebar).should('be.visible');\n cy.getByTestId(pkg.installList).within(() => {\n cy.getByTestId(pkg.installNpm).should('be.visible');\n cy.getByTestId(pkg.installYarn).should('be.visible');\n cy.getByTestId(pkg.installPnpm).should('be.visible');\n });\n cy.getByTestId(pkg.installNpm).should('contain.text', pkgName);\n });\n\n it('should render the sidebar keywords from the published manifest', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n // publishPackage writes `keywords: ['verdaccio', 'e2e', 'test']`\n // into the generated package.json.\n cy.getByTestId(pkg.keywordList).should('be.visible');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'verdaccio');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'e2e');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'test');\n });\n\n it('should click on dependencies tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.dependenciesTab).click();\n cy.wait(100);\n cy.getByTestId(pkg.dependencies).should('have.length', 1);\n // The dep is rendered with its name as testid. This is a dynamic\n // Verdaccio convention (not configurable via testIds map).\n cy.getByTestId('debug').children().invoke('text').should('match', /debug/);\n });\n\n it('should click on versions tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.versionsTab).click();\n // With `unique: true` the version becomes `1.0.0-t<timestamp>`,\n // but \"1.0.0\" still appears as a substring — match loosely.\n cy.getByTestId(pkg.tagLatest)\n .children()\n .invoke('text')\n .should('match', /1\\.0\\.0/);\n });\n\n it('should click on uplinks tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.uplinksTab).click();\n cy.getByTestId(pkg.noUplinks).should('be.visible');\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"publish.cjs","names":[],"sources":["../../../src/tests/publish.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\nexport function publishTests(config: RegistryConfig) {\n const { header, package: pkg } = config.testIds;\n const { markdownBody, loginDialog } = config.selectors;\n const { features } = config;\n\n describe('publish', () => {\n const pkgName = '@verdaccio/pkg-scoped';\n // Single source of truth for the dependency the publish fixture\n // writes into its package.json. The dependencies-tab assertion\n // reads these back to verify the UI rendered both fields.\n const depName = 'debug';\n const depVersion = '4.0.0';\n // Per-test state so afterEach can clean up the specific publish\n // that this test created (temp folder + registry entry).\n let tempFolder: string | null = null;\n\n /**\n * Log in once, reuse the session across every test in this suite.\n * `cy.session` caches cookies + localStorage keyed on the first\n * argument, so subsequent calls restore without hitting the network.\n */\n const loginOnce = () => {\n cy.session(\n ['publish-suite', config.credentials.user],\n () => {\n cy.visit(config.registryUrl);\n cy.login(config.credentials.user, config.credentials.password, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@sign');\n },\n {\n validate() {\n cy.request({\n url: `${config.registryUrl}/-/verdaccio/data/packages`,\n failOnStatusCode: false,\n })\n .its('status')\n .should('be.oneOf', [200, 304]);\n },\n cacheAcrossSpecs: true,\n }\n );\n };\n\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n cy.intercept('GET', '/-/verdaccio/data/packages').as('pkgs');\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}`).as('sidebar');\n cy.intercept('GET', `/-/verdaccio/data/package/readme/${pkgName}`).as('readme');\n\n // Publish a fresh copy for this test. `unique: true` appends a\n // timestamp suffix so the version is distinct per test even when\n // the registry briefly has a stale copy from a prior afterEach.\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n dependencies: { [depName]: depVersion },\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n\n loginOnce();\n cy.visit(config.registryUrl);\n });\n\n afterEach(() => {\n // Remove the package from the registry AND the local temp folder.\n // Run both regardless of test outcome so the next test starts\n // clean. `unpublishPackage` treats 404 as success, so a re-run\n // after a half-published state is still safe.\n cy.task('unpublishPackage', { pkgName, tempFolder: tempFolder ?? undefined });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n it('should have one published package', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).should('have.length.at.least', 1);\n });\n\n it('should navigate to page detail', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n });\n\n it('should have readme content', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.readme).should('be.visible');\n cy.get(markdownBody).should('have.length', 1);\n // publishPackage writes a README whose body contains \"e2e testing\".\n cy.contains(markdownBody, /test/);\n cy.contains(`${markdownBody} h1`, pkgName).should('be.visible');\n });\n\n it('should render the sidebar with install commands for npm, yarn, pnpm', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n cy.getByTestId(pkg.sidebar).should('be.visible');\n cy.getByTestId(pkg.installList).within(() => {\n cy.getByTestId(pkg.installNpm).should('be.visible');\n cy.getByTestId(pkg.installYarn).should('be.visible');\n cy.getByTestId(pkg.installPnpm).should('be.visible');\n });\n cy.getByTestId(pkg.installNpm).should('contain.text', pkgName);\n });\n\n it('should render the sidebar keywords from the published manifest', () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n // publishPackage writes `keywords: ['verdaccio', 'e2e', 'test']`\n // into the generated package.json.\n cy.getByTestId(pkg.keywordList).should('be.visible');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'verdaccio');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'e2e');\n cy.getByTestId(pkg.keywordList).should('contain.text', 'test');\n });\n\n it('should click on dependencies tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.dependenciesTab).click();\n cy.wait(100);\n cy.getByTestId(pkg.dependencies).should('have.length', 1);\n\n // The dep Chip uses the dep name as its data-testid (dynamic\n // Verdaccio convention, see DependencyBlock.tsx:68), and its\n // label is `\"${name}: ${version}\"` via the `dependencies.\n // dependency-block` i18n key. Assert BOTH fields are rendered.\n cy.getByTestId(depName)\n .should('be.visible')\n .and('contain.text', depName)\n .and('contain.text', depVersion);\n\n // Also verify the Chip text matches the exact \"name: version\"\n // format so a regression in the label template would fail here\n // rather than pass via a loose substring match.\n cy.getByTestId(depName)\n .invoke('text')\n .should('match', new RegExp(`${depName}\\\\s*:\\\\s*${depVersion}`));\n });\n\n it('should click on versions tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.versionsTab).click();\n // With `unique: true` the version becomes `1.0.0-t<timestamp>`,\n // but \"1.0.0\" still appears as a substring — match loosely.\n cy.getByTestId(pkg.tagLatest)\n .children()\n .invoke('text')\n .should('match', /1\\.0\\.0/);\n });\n\n it('should click on uplinks tab', () => {\n cy.wait('@pkgs');\n cy.wait(300);\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@readme');\n cy.wait('@sidebar');\n cy.getByTestId(pkg.uplinksTab).click();\n cy.getByTestId(pkg.noUplinks).should('be.visible');\n });\n\n // ── Action-bar FABs: tarball download + raw viewer ─────────────\n // Both buttons live in the sidebar ActionBar. They're gated on\n // `web.showDownloadTarball` / `web.showRaw` (both default to true\n // in the ui-theme's AppConfigurationProvider) and each test is\n // also guarded by a feature flag so branches that ship a\n // different action bar can skip cleanly.\n\n maybeIt(features.publish.downloadTarball)(\n 'should fetch the tarball when the download button is clicked',\n () => {\n // The download provider hits the package manifest's dist.tarball\n // URL directly. For our published fixture the filename looks\n // like `pkg-scoped-1.0.0-t<ts>.tgz`, served from\n // `/<pkg>/-/<filename>.tgz`. Intercept before clicking.\n cy.intercept('GET', '**/pkg-scoped-*.tgz').as('tarballFetch');\n\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n\n cy.getByTestId(pkg.downloadTarballBtn)\n .should('be.visible')\n .click();\n\n // The fetch should fire and return 200. We can't assert on the\n // actual file landing on disk — Cypress doesn't track OS-level\n // downloads — but a successful GET proves the end-to-end path\n // from click → download provider → registry.\n cy.wait('@tarballFetch', { timeout: 10000 })\n .its('response.statusCode')\n .should('eq', 200);\n }\n );\n\n maybeIt(features.publish.rawViewer)(\n 'should open the raw manifest viewer when the raw button is clicked',\n () => {\n cy.wait('@pkgs');\n cy.getByTestId(pkg.title).first().click();\n cy.wait('@sidebar');\n\n // RawViewer is a full-screen MUI Dialog — initially unmounted\n // because `isOpen=false` keeps Dialog closed and Cypress won't\n // find it. Clicking the FAB flips `isOpen` to true.\n cy.getByTestId(pkg.rawBtn).should('be.visible').click();\n\n cy.getByTestId(pkg.rawViewerDialog, { timeout: 5000 }).should(\n 'be.visible'\n );\n // The ReactJson viewer renders the package manifest — the\n // package name should appear somewhere in the serialized JSON.\n cy.getByTestId(pkg.rawViewerDialog).should('contain.text', pkgName);\n\n // Close via the X button and confirm the dialog goes away so\n // subsequent tests don't inherit an open overlay.\n cy.getByTestId(pkg.closeRawViewer).click();\n cy.getByTestId(pkg.rawViewerDialog).should('not.exist');\n }\n );\n });\n}\n"],"mappings":";;AAKA,SAAgB,aAAa,QAAwB;CACnD,MAAM,EAAE,QAAQ,SAAS,QAAQ,OAAO;CACxC,MAAM,EAAE,cAAc,gBAAgB,OAAO;CAC7C,MAAM,EAAE,aAAa;AAErB,UAAS,iBAAiB;EACxB,MAAM,UAAU;EAIhB,MAAM,UAAU;EAChB,MAAM,aAAa;EAGnB,IAAI,aAA4B;;;;;;EAOhC,MAAM,kBAAkB;AACtB,MAAG,QACD,CAAC,iBAAiB,OAAO,YAAY,KAAK,QACpC;AACJ,OAAG,MAAM,OAAO,YAAY;AAC5B,OAAG,MAAM,OAAO,YAAY,MAAM,OAAO,YAAY,UAAU;KAC7D,aAAa,OAAO;KACpB,GAAG;KACJ,CAAC;AACF,OAAG,KAAK,QAAQ;MAElB;IACE,WAAW;AACT,QAAG,QAAQ;MACT,KAAK,GAAG,OAAO,YAAY;MAC3B,kBAAkB;MACnB,CAAC,CACC,IAAI,SAAS,CACb,OAAO,YAAY,CAAC,KAAK,IAAI,CAAC;;IAEnC,kBAAkB;IACnB,CACF;;AAGH,mBAAiB;AACf,MAAG,UAAU,QAAQ,yBAAyB,CAAC,GAAG,OAAO;AACzD,MAAG,UAAU,OAAO,6BAA6B,CAAC,GAAG,OAAO;AAC5D,MAAG,UAAU,OAAO,6BAA6B,UAAU,CAAC,GAAG,UAAU;AACzE,MAAG,UAAU,OAAO,oCAAoC,UAAU,CAAC,GAAG,SAAS;AAK/E,MAAG,KAAK,kBAAkB;IACxB;IACA,SAAS;IACT,cAAc,GAAG,UAAU,YAAY;IACvC,QAAQ;IACT,CAAC,CAAC,MAAM,WAAW;AAClB,iBAAa,QAAQ,cAAc;KACnC;AAEF,cAAW;AACX,MAAG,MAAM,OAAO,YAAY;IAC5B;AAEF,kBAAgB;AAKd,MAAG,KAAK,oBAAoB;IAAE;IAAS,YAAY,cAAc,KAAA;IAAW,CAAC;AAC7E,OAAI,WACF,IAAG,KAAK,oBAAoB,WAAW;AAEzC,gBAAa;IACb;AAEF,KAAG,2CAA2C;AAC5C,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,wBAAwB,EAAE;IAC3D;AAEF,KAAG,wCAAwC;AACzC,MAAG,KAAK,QAAQ;AAChB,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;IACzC;AAEF,KAAG,oCAAoC;AACrC,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,UAAU;AAClB,MAAG,KAAK,WAAW;AACnB,MAAG,YAAY,IAAI,OAAO,CAAC,OAAO,aAAa;AAC/C,MAAG,IAAI,aAAa,CAAC,OAAO,eAAe,EAAE;AAE7C,MAAG,SAAS,cAAc,OAAO;AACjC,MAAG,SAAS,GAAG,aAAa,MAAM,QAAQ,CAAC,OAAO,aAAa;IAC/D;AAEF,KAAG,6EAA6E;AAC9E,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,WAAW;AACnB,MAAG,YAAY,IAAI,QAAQ,CAAC,OAAO,aAAa;AAChD,MAAG,YAAY,IAAI,YAAY,CAAC,aAAa;AAC3C,OAAG,YAAY,IAAI,WAAW,CAAC,OAAO,aAAa;AACnD,OAAG,YAAY,IAAI,YAAY,CAAC,OAAO,aAAa;AACpD,OAAG,YAAY,IAAI,YAAY,CAAC,OAAO,aAAa;KACpD;AACF,MAAG,YAAY,IAAI,WAAW,CAAC,OAAO,gBAAgB,QAAQ;IAC9D;AAEF,KAAG,wEAAwE;AACzE,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,WAAW;AAGnB,MAAG,YAAY,IAAI,YAAY,CAAC,OAAO,aAAa;AACpD,MAAG,YAAY,IAAI,YAAY,CAAC,OAAO,gBAAgB,YAAY;AACnE,MAAG,YAAY,IAAI,YAAY,CAAC,OAAO,gBAAgB,MAAM;AAC7D,MAAG,YAAY,IAAI,YAAY,CAAC,OAAO,gBAAgB,OAAO;IAC9D;AAEF,KAAG,0CAA0C;AAC3C,MAAG,KAAK,QAAQ;AAChB,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,UAAU;AAClB,MAAG,KAAK,WAAW;AACnB,MAAG,YAAY,IAAI,gBAAgB,CAAC,OAAO;AAC3C,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,IAAI,aAAa,CAAC,OAAO,eAAe,EAAE;AAMzD,MAAG,YAAY,QAAQ,CACpB,OAAO,aAAa,CACpB,IAAI,gBAAgB,QAAQ,CAC5B,IAAI,gBAAgB,WAAW;AAKlC,MAAG,YAAY,QAAQ,CACpB,OAAO,OAAO,CACd,OAAO,SAAS,IAAI,OAAO,GAAG,QAAQ,WAAW,aAAa,CAAC;IAClE;AAEF,KAAG,sCAAsC;AACvC,MAAG,KAAK,QAAQ;AAChB,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,UAAU;AAClB,MAAG,KAAK,WAAW;AACnB,MAAG,YAAY,IAAI,YAAY,CAAC,OAAO;AAGvC,MAAG,YAAY,IAAI,UAAU,CAC1B,UAAU,CACV,OAAO,OAAO,CACd,OAAO,SAAS,UAAU;IAC7B;AAEF,KAAG,qCAAqC;AACtC,MAAG,KAAK,QAAQ;AAChB,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,UAAU;AAClB,MAAG,KAAK,WAAW;AACnB,MAAG,YAAY,IAAI,WAAW,CAAC,OAAO;AACtC,MAAG,YAAY,IAAI,UAAU,CAAC,OAAO,aAAa;IAClD;AASF,mBAAA,QAAQ,SAAS,QAAQ,gBAAgB,CACvC,sEACM;AAKJ,MAAG,UAAU,OAAO,sBAAsB,CAAC,GAAG,eAAe;AAE7D,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,WAAW;AAEnB,MAAG,YAAY,IAAI,mBAAmB,CACnC,OAAO,aAAa,CACpB,OAAO;AAMV,MAAG,KAAK,iBAAiB,EAAE,SAAS,KAAO,CAAC,CACzC,IAAI,sBAAsB,CAC1B,OAAO,MAAM,IAAI;IAEvB;AAED,mBAAA,QAAQ,SAAS,QAAQ,UAAU,CACjC,4EACM;AACJ,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO;AACzC,MAAG,KAAK,WAAW;AAKnB,MAAG,YAAY,IAAI,OAAO,CAAC,OAAO,aAAa,CAAC,OAAO;AAEvD,MAAG,YAAY,IAAI,iBAAiB,EAAE,SAAS,KAAM,CAAC,CAAC,OACrD,aACD;AAGD,MAAG,YAAY,IAAI,gBAAgB,CAAC,OAAO,gBAAgB,QAAQ;AAInE,MAAG,YAAY,IAAI,eAAe,CAAC,OAAO;AAC1C,MAAG,YAAY,IAAI,gBAAgB,CAAC,OAAO,YAAY;IAE1D;GACD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
1
2
|
//#region src/tests/search.ts
|
|
2
3
|
/**
|
|
3
4
|
* UI tests for the Verdaccio search box.
|
|
@@ -8,6 +9,8 @@
|
|
|
8
9
|
* a real package in the registry should live in publishTests instead.
|
|
9
10
|
*/
|
|
10
11
|
function searchTests(config) {
|
|
12
|
+
const { features } = config;
|
|
13
|
+
const { package: pkg } = config.testIds;
|
|
11
14
|
describe("search", () => {
|
|
12
15
|
beforeEach(() => {
|
|
13
16
|
cy.intercept("GET", "**/-/verdaccio/data/search/**").as("webSearch");
|
|
@@ -43,6 +46,51 @@ function searchTests(config) {
|
|
|
43
46
|
expect(interception.request.url).to.contain("second-query");
|
|
44
47
|
});
|
|
45
48
|
});
|
|
49
|
+
describe("with a published package", () => {
|
|
50
|
+
const pkgName = "@verdaccio/search-fixture";
|
|
51
|
+
const pkgSlug = "search-fixture";
|
|
52
|
+
let tempFolder = null;
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
cy.task("publishPackage", {
|
|
55
|
+
pkgName,
|
|
56
|
+
version: "1.0.0",
|
|
57
|
+
unique: true
|
|
58
|
+
}).then((result) => {
|
|
59
|
+
tempFolder = result?.tempFolder ?? null;
|
|
60
|
+
});
|
|
61
|
+
cy.visit(config.registryUrl);
|
|
62
|
+
});
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
cy.task("unpublishPackage", {
|
|
65
|
+
pkgName,
|
|
66
|
+
tempFolder: tempFolder ?? void 0
|
|
67
|
+
});
|
|
68
|
+
if (tempFolder) cy.task("cleanupPublished", tempFolder);
|
|
69
|
+
tempFolder = null;
|
|
70
|
+
});
|
|
71
|
+
require_features.maybeIt(features.search.resultsDropdown)("should display the matching package in the results dropdown", () => {
|
|
72
|
+
getSearchInput().clear().type(pkgSlug, { delay: 30 });
|
|
73
|
+
cy.wait(anySearchAlias(), { timeout: 1e4 }).then((interception) => {
|
|
74
|
+
expect(interception.request.url).to.contain(pkgSlug);
|
|
75
|
+
});
|
|
76
|
+
cy.get("[role=\"listbox\"]", { timeout: 5e3 }).should("be.visible");
|
|
77
|
+
cy.get("[role=\"listbox\"] [role=\"option\"]").should("have.length.at.least", 1);
|
|
78
|
+
cy.contains("[role=\"listbox\"] [role=\"option\"]", pkgName).should("be.visible");
|
|
79
|
+
});
|
|
80
|
+
require_features.maybeIt(features.search.resultClickNavigation)("should navigate to the package detail page when a result is clicked", () => {
|
|
81
|
+
cy.intercept("GET", `/-/verdaccio/data/sidebar/${pkgName}`).as("detailSidebar");
|
|
82
|
+
cy.intercept("GET", `/-/verdaccio/data/package/readme/${pkgName}`).as("detailReadme");
|
|
83
|
+
getSearchInput().clear().type(pkgSlug, { delay: 30 });
|
|
84
|
+
cy.wait(anySearchAlias(), { timeout: 1e4 });
|
|
85
|
+
cy.contains("[role=\"listbox\"] [role=\"option\"]", pkgName).should("be.visible").click();
|
|
86
|
+
cy.location("pathname").should("contain", "/-/web/detail");
|
|
87
|
+
cy.location("pathname").should("contain", "search-fixture");
|
|
88
|
+
cy.wait("@detailSidebar", { timeout: 1e4 });
|
|
89
|
+
cy.wait("@detailReadme", { timeout: 1e4 });
|
|
90
|
+
cy.getByTestId(pkg.sidebar).should("be.visible");
|
|
91
|
+
cy.getByTestId(pkg.readme).should("be.visible");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
46
94
|
});
|
|
47
95
|
}
|
|
48
96
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.cjs","names":[],"sources":["../../../src/tests/search.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { RegistryConfig } from '../types';\n\n/**\n * UI tests for the Verdaccio search box.\n *\n * These tests do NOT depend on any published package — they assert the\n * search *flow* (input exists, typing triggers the search API, results\n * region updates) rather than specific package metadata. Tests that need\n * a real package in the registry should live in publishTests instead.\n */\nexport function searchTests(config: RegistryConfig) {\n describe('search', () => {\n beforeEach(() => {\n // Verdaccio's search endpoint — covers both the web API and the\n // npm v1 search API, depending on which one the UI calls.\n cy.intercept('GET', '**/-/verdaccio/data/search/**').as('webSearch');\n cy.intercept('GET', '**/-/v1/search**').as('v1Search');\n cy.intercept('GET', '**/-/verdaccio/data/packages').as('pkgs');\n\n cy.visit(config.registryUrl);\n cy.get('body').should('be.visible');\n });\n\n it('should render the search input', () => {\n getSearchInput().should('be.visible');\n });\n\n it('should fire a search request when typing a query', () => {\n const query = 'verdaccio';\n\n getSearchInput().clear().type(query, { delay: 30 });\n\n // Whichever endpoint the UI is wired to, at least one should hit.\n cy.wait(anySearchAlias(), { timeout: 10000 }).then((interception: any) => {\n expect(interception.request.url).to.contain(query);\n });\n });\n\n it('should show a \"no results\" state for an impossible query', () => {\n const query = 'xyzzy-no-such-package-' + Date.now();\n\n getSearchInput().clear().type(query, { delay: 30 });\n cy.wait(anySearchAlias(), { timeout: 10000 });\n\n // Give the UI a tick to render the empty state.\n cy.wait(300);\n\n // The UI may render \"No Match\", \"No results\", or similar — match\n // loosely rather than binding to one exact string.\n cy.contains(/no\\s+(match|results|packages)/i, { timeout: 5000 }).should(\n 'be.visible'\n );\n });\n\n it('should clear the query and allow typing a new one', () => {\n getSearchInput().clear().type('first-query', { delay: 20 });\n cy.wait(anySearchAlias(), { timeout: 10000 });\n\n // Clearing should empty the input value. We deliberately do NOT\n // assert on the autocomplete dropdown's empty-state text: on a\n // registry with no packages published it lingers regardless of\n // the input value, which previously caused a false positive.\n getSearchInput().clear();\n getSearchInput().should('have.value', '');\n\n // Typing a fresh query must fire another search request so the\n // search box is still functional after a clear.\n getSearchInput().type('second-query', { delay: 20 });\n cy.wait(anySearchAlias(), { timeout: 10000 }).then(\n (interception: any) => {\n expect(interception.request.url).to.contain('second-query');\n }\n );\n });\n });\n}\n\n/**\n * Resolve the search input. Verdaccio 6 renders it with different\n * data-testid values across versions, so we try a few in order before\n * falling back to a generic role/type selector.\n */\nfunction getSearchInput() {\n return cy.get(\n [\n '[data-testid=\"search-input\"]',\n '[data-testid=\"header--input-search\"]',\n '[data-testid=\"autoCompleteSearch\"] input',\n 'input[aria-label*=\"earch\"]',\n 'input[placeholder*=\"earch\"]',\n 'input[type=\"search\"]',\n ].join(', '),\n { timeout: 10000 }\n );\n}\n\n/**\n * Cypress `cy.wait` only accepts one alias at a time, so pick whichever\n * alias fires first. This helper lets us stay agnostic to which search\n * endpoint the UI is wired to.\n */\nfunction anySearchAlias(): string {\n // In practice most Verdaccio 6 builds call the web search endpoint;\n // prefer that one and let the test fail loud if neither fires.\n return '@webSearch';\n}\n"],"mappings":";;;;;;;;;AAYA,SAAgB,YAAY,QAAwB;AAClD,UAAS,gBAAgB;AACvB,mBAAiB;AAGf,MAAG,UAAU,OAAO,gCAAgC,CAAC,GAAG,YAAY;AACpE,MAAG,UAAU,OAAO,mBAAmB,CAAC,GAAG,WAAW;AACtD,MAAG,UAAU,OAAO,+BAA+B,CAAC,GAAG,OAAO;AAE9D,MAAG,MAAM,OAAO,YAAY;AAC5B,MAAG,IAAI,OAAO,CAAC,OAAO,aAAa;IACnC;AAEF,KAAG,wCAAwC;AACzC,mBAAgB,CAAC,OAAO,aAAa;IACrC;AAEF,KAAG,0DAA0D;GAC3D,MAAM,QAAQ;AAEd,mBAAgB,CAAC,OAAO,CAAC,KAAK,OAAO,EAAE,OAAO,IAAI,CAAC;AAGnD,MAAG,KAAK,gBAAgB,EAAE,EAAE,SAAS,KAAO,CAAC,CAAC,MAAM,iBAAsB;AACxE,WAAO,aAAa,QAAQ,IAAI,CAAC,GAAG,QAAQ,MAAM;KAClD;IACF;AAEF,KAAG,oEAAkE;GACnE,MAAM,QAAQ,2BAA2B,KAAK,KAAK;AAEnD,mBAAgB,CAAC,OAAO,CAAC,KAAK,OAAO,EAAE,OAAO,IAAI,CAAC;AACnD,MAAG,KAAK,gBAAgB,EAAE,EAAE,SAAS,KAAO,CAAC;AAG7C,MAAG,KAAK,IAAI;AAIZ,MAAG,SAAS,kCAAkC,EAAE,SAAS,KAAM,CAAC,CAAC,OAC/D,aACD;IACD;AAEF,KAAG,2DAA2D;AAC5D,mBAAgB,CAAC,OAAO,CAAC,KAAK,eAAe,EAAE,OAAO,IAAI,CAAC;AAC3D,MAAG,KAAK,gBAAgB,EAAE,EAAE,SAAS,KAAO,CAAC;AAM7C,mBAAgB,CAAC,OAAO;AACxB,mBAAgB,CAAC,OAAO,cAAc,GAAG;AAIzC,mBAAgB,CAAC,KAAK,gBAAgB,EAAE,OAAO,IAAI,CAAC;AACpD,MAAG,KAAK,gBAAgB,EAAE,EAAE,SAAS,KAAO,CAAC,CAAC,MAC3C,iBAAsB;AACrB,WAAO,aAAa,QAAQ,IAAI,CAAC,GAAG,QAAQ,eAAe;KAE9D;IACD;GACF;;;;;;;AAQJ,SAAS,iBAAiB;AACxB,QAAO,GAAG,IACR;EACE;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK,EACZ,EAAE,SAAS,KAAO,CACnB;;;;;;;AAQH,SAAS,iBAAyB;AAGhC,QAAO"}
|
|
1
|
+
{"version":3,"file":"search.cjs","names":[],"sources":["../../../src/tests/search.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * UI tests for the Verdaccio search box.\n *\n * These tests do NOT depend on any published package — they assert the\n * search *flow* (input exists, typing triggers the search API, results\n * region updates) rather than specific package metadata. Tests that need\n * a real package in the registry should live in publishTests instead.\n */\nexport function searchTests(config: RegistryConfig) {\n const { features } = config;\n const { package: pkg } = config.testIds;\n\n describe('search', () => {\n beforeEach(() => {\n // Verdaccio's search endpoint — covers both the web API and the\n // npm v1 search API, depending on which one the UI calls.\n cy.intercept('GET', '**/-/verdaccio/data/search/**').as('webSearch');\n cy.intercept('GET', '**/-/v1/search**').as('v1Search');\n cy.intercept('GET', '**/-/verdaccio/data/packages').as('pkgs');\n\n cy.visit(config.registryUrl);\n cy.get('body').should('be.visible');\n });\n\n it('should render the search input', () => {\n getSearchInput().should('be.visible');\n });\n\n it('should fire a search request when typing a query', () => {\n const query = 'verdaccio';\n\n getSearchInput().clear().type(query, { delay: 30 });\n\n // Whichever endpoint the UI is wired to, at least one should hit.\n cy.wait(anySearchAlias(), { timeout: 10000 }).then((interception: any) => {\n expect(interception.request.url).to.contain(query);\n });\n });\n\n it('should show a \"no results\" state for an impossible query', () => {\n const query = 'xyzzy-no-such-package-' + Date.now();\n\n getSearchInput().clear().type(query, { delay: 30 });\n cy.wait(anySearchAlias(), { timeout: 10000 });\n\n // Give the UI a tick to render the empty state.\n cy.wait(300);\n\n // The UI may render \"No Match\", \"No results\", or similar — match\n // loosely rather than binding to one exact string.\n cy.contains(/no\\s+(match|results|packages)/i, { timeout: 5000 }).should(\n 'be.visible'\n );\n });\n\n it('should clear the query and allow typing a new one', () => {\n getSearchInput().clear().type('first-query', { delay: 20 });\n cy.wait(anySearchAlias(), { timeout: 10000 });\n\n // Clearing should empty the input value. We deliberately do NOT\n // assert on the autocomplete dropdown's empty-state text: on a\n // registry with no packages published it lingers regardless of\n // the input value, which previously caused a false positive.\n getSearchInput().clear();\n getSearchInput().should('have.value', '');\n\n // Typing a fresh query must fire another search request so the\n // search box is still functional after a clear.\n getSearchInput().type('second-query', { delay: 20 });\n cy.wait(anySearchAlias(), { timeout: 10000 }).then(\n (interception: any) => {\n expect(interception.request.url).to.contain('second-query');\n }\n );\n });\n\n // ── Rendering assertions that require real package data ────────\n // Publishes a throwaway package before each test and unpublishes\n // after so the outer \"no results\" test still sees a clean registry.\n // The search query uses a substring of the package name to avoid\n // scope-parsing issues with `@` / `/` characters in the URL.\n describe('with a published package', () => {\n const pkgName = '@verdaccio/search-fixture';\n // Unique slug we can type into the search box — must be a\n // substring of pkgName so Verdaccio's search matches it.\n const pkgSlug = 'search-fixture';\n let tempFolder: string | null = null;\n\n beforeEach(() => {\n cy.task('publishPackage', {\n pkgName,\n version: '1.0.0',\n unique: true,\n }).then((result) => {\n tempFolder = result?.tempFolder ?? null;\n });\n cy.visit(config.registryUrl);\n });\n\n afterEach(() => {\n cy.task('unpublishPackage', {\n pkgName,\n tempFolder: tempFolder ?? undefined,\n });\n if (tempFolder) {\n cy.task('cleanupPublished', tempFolder);\n }\n tempFolder = null;\n });\n\n maybeIt(features.search.resultsDropdown)(\n 'should display the matching package in the results dropdown',\n () => {\n getSearchInput().clear().type(pkgSlug, { delay: 30 });\n\n // Wait for the search request to resolve with results.\n cy.wait(anySearchAlias(), { timeout: 10000 }).then(\n (interception: any) => {\n expect(interception.request.url).to.contain(pkgSlug);\n }\n );\n\n // MUI Autocomplete opens a listbox with role=\"listbox\" when\n // there are matching options. Each result renders with\n // role=\"option\". No data-testids on the dropdown itself, so\n // we lean on the ARIA roles which are stable across MUI\n // versions.\n cy.get('[role=\"listbox\"]', { timeout: 5000 }).should('be.visible');\n cy.get('[role=\"listbox\"] [role=\"option\"]').should(\n 'have.length.at.least',\n 1\n );\n // The result item must contain the full package name.\n cy.contains('[role=\"listbox\"] [role=\"option\"]', pkgName).should(\n 'be.visible'\n );\n }\n );\n\n maybeIt(features.search.resultClickNavigation)(\n 'should navigate to the package detail page when a result is clicked',\n () => {\n // Intercept the two data endpoints that the detail route\n // fetches on mount. Waiting on these is the most reliable\n // way to know the router actually resolved the new page\n // (not just changed the URL).\n cy.intercept('GET', `/-/verdaccio/data/sidebar/${pkgName}`).as(\n 'detailSidebar'\n );\n cy.intercept(\n 'GET',\n `/-/verdaccio/data/package/readme/${pkgName}`\n ).as('detailReadme');\n\n getSearchInput().clear().type(pkgSlug, { delay: 30 });\n cy.wait(anySearchAlias(), { timeout: 10000 });\n\n cy.contains('[role=\"listbox\"] [role=\"option\"]', pkgName)\n .should('be.visible')\n .click();\n\n // Verdaccio routes package detail under /-/web/detail/<pkg>.\n cy.location('pathname').should('contain', '/-/web/detail');\n cy.location('pathname').should('contain', 'search-fixture');\n\n // Wait for the detail page's own fetches to settle so\n // assertions don't race the async content.\n cy.wait('@detailSidebar', { timeout: 10000 });\n cy.wait('@detailReadme', { timeout: 10000 });\n\n // Detail page rendered both panes end-to-end.\n cy.getByTestId(pkg.sidebar).should('be.visible');\n cy.getByTestId(pkg.readme).should('be.visible');\n }\n );\n });\n });\n}\n\n/**\n * Resolve the search input. Verdaccio 6 renders it with different\n * data-testid values across versions, so we try a few in order before\n * falling back to a generic role/type selector.\n */\nfunction getSearchInput() {\n return cy.get(\n [\n '[data-testid=\"search-input\"]',\n '[data-testid=\"header--input-search\"]',\n '[data-testid=\"autoCompleteSearch\"] input',\n 'input[aria-label*=\"earch\"]',\n 'input[placeholder*=\"earch\"]',\n 'input[type=\"search\"]',\n ].join(', '),\n { timeout: 10000 }\n );\n}\n\n/**\n * Cypress `cy.wait` only accepts one alias at a time, so pick whichever\n * alias fires first. This helper lets us stay agnostic to which search\n * endpoint the UI is wired to.\n */\nfunction anySearchAlias(): string {\n // In practice most Verdaccio 6 builds call the web search endpoint;\n // prefer that one and let the test fail loud if neither fires.\n return '@webSearch';\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
1
2
|
//#region src/tests/settings.ts
|
|
2
3
|
/**
|
|
3
4
|
* Tests for the header Settings dialog and the Translations (language)
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
*/
|
|
19
20
|
function settingsTests(config) {
|
|
20
21
|
const { header } = config.testIds;
|
|
22
|
+
const { features } = config;
|
|
21
23
|
describe("settings & language", () => {
|
|
22
24
|
beforeEach(() => {
|
|
23
25
|
cy.visit(config.registryUrl);
|
|
@@ -42,7 +44,7 @@ function settingsTests(config) {
|
|
|
42
44
|
cy.contains("[role=\"dialog\"]", "English").should("be.visible");
|
|
43
45
|
cy.contains("[role=\"dialog\"]", "German").should("be.visible");
|
|
44
46
|
});
|
|
45
|
-
|
|
47
|
+
require_features.maybeIt(features.settings.languageSwitcher)("should change the UI language when a language card is clicked", () => {
|
|
46
48
|
cy.getByTestId(header.settingsTooltip).click();
|
|
47
49
|
cy.get("[role=\"dialog\"]").should("be.visible");
|
|
48
50
|
cy.contains("[role=\"dialog\"] [role=\"tab\"]", "Translations").click();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.cjs","names":[],"sources":["../../../src/tests/settings.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { RegistryConfig } from '../types';\n\n/**\n * Tests for the header Settings dialog and the Translations (language)\n * picker inside it.\n *\n * Requires `web.showSettings: true` in the registry config — the gear\n * icon only renders when the flag is on (see HeaderRight in Verdaccio's\n * ui-components package).\n *\n * Selector strategy: the language cards in LanguageSwitch do NOT have\n * data-testids, so this suite leans on:\n * - MUI's ARIA roles: `[role=\"dialog\"]`, `[role=\"tab\"]`\n * - The stable English labels \"Configuration\", \"Translations\",\n * \"English\", \"German\", and the language description text, which are\n * bundled into the ui-theme via `@verdaccio/ui-i18n`\n * - The settings trigger's testid from Verdaccio's HeaderToolTipIcon:\n * `header--tooltip-settings`\n */\nexport function settingsTests(config: RegistryConfig) {\n const { header } = config.testIds;\n\n describe('settings & language', () => {\n beforeEach(() => {\n cy.visit(config.registryUrl);\n cy.get('body').should('be.visible');\n });\n\n it('should open the settings dialog from the header', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n // Dialog title key: dialog.settings.title → \"Configuration\" in EN.\n cy.contains('[role=\"dialog\"]', 'Configuration').should('be.visible');\n });\n\n it('should show both Package Managers and Translations tabs', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.get('[role=\"dialog\"] [role=\"tab\"]').should('have.length', 2);\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').should(\n 'be.visible'\n );\n });\n\n it('should switch to the Translations tab and list languages', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n // language.description text is bundled in EN — good sentinel.\n cy.contains(\n '[role=\"dialog\"]',\n /this is the configuration details for the language/i\n ).should('be.visible');\n // Sanity-check a couple of language cards are rendered.\n cy.contains('[role=\"dialog\"]', 'English').should('be.visible');\n cy.contains('[role=\"dialog\"]', 'German').should('be.visible');\n });\n\n
|
|
1
|
+
{"version":3,"file":"settings.cjs","names":[],"sources":["../../../src/tests/settings.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\n/**\n * Tests for the header Settings dialog and the Translations (language)\n * picker inside it.\n *\n * Requires `web.showSettings: true` in the registry config — the gear\n * icon only renders when the flag is on (see HeaderRight in Verdaccio's\n * ui-components package).\n *\n * Selector strategy: the language cards in LanguageSwitch do NOT have\n * data-testids, so this suite leans on:\n * - MUI's ARIA roles: `[role=\"dialog\"]`, `[role=\"tab\"]`\n * - The stable English labels \"Configuration\", \"Translations\",\n * \"English\", \"German\", and the language description text, which are\n * bundled into the ui-theme via `@verdaccio/ui-i18n`\n * - The settings trigger's testid from Verdaccio's HeaderToolTipIcon:\n * `header--tooltip-settings`\n */\nexport function settingsTests(config: RegistryConfig) {\n const { header } = config.testIds;\n const { features } = config;\n\n describe('settings & language', () => {\n beforeEach(() => {\n cy.visit(config.registryUrl);\n cy.get('body').should('be.visible');\n });\n\n it('should open the settings dialog from the header', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n // Dialog title key: dialog.settings.title → \"Configuration\" in EN.\n cy.contains('[role=\"dialog\"]', 'Configuration').should('be.visible');\n });\n\n it('should show both Package Managers and Translations tabs', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.get('[role=\"dialog\"] [role=\"tab\"]').should('have.length', 2);\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').should(\n 'be.visible'\n );\n });\n\n it('should switch to the Translations tab and list languages', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n // language.description text is bundled in EN — good sentinel.\n cy.contains(\n '[role=\"dialog\"]',\n /this is the configuration details for the language/i\n ).should('be.visible');\n // Sanity-check a couple of language cards are rendered.\n cy.contains('[role=\"dialog\"]', 'English').should('be.visible');\n cy.contains('[role=\"dialog\"]', 'German').should('be.visible');\n });\n\n maybeIt(features.settings.languageSwitcher)(\n 'should change the UI language when a language card is clicked',\n () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').click();\n\n // Before switching, capture the current dialog title so we can\n // assert it changes. This avoids hard-coding the German string.\n cy.contains('[role=\"dialog\"]', 'Configuration')\n .invoke('text')\n .then((englishTitle) => {\n // Click the German card. We scope to the dialog + the MUI\n // card class to avoid matching other \"German\" text.\n cy.contains('[role=\"dialog\"] .MuiCard-root', 'German').click({\n force: true,\n });\n\n // The English tab label \"Translations\" must no longer be\n // present anywhere in the dialog — strongest proof the\n // language actually switched.\n cy.contains('[role=\"dialog\"] [role=\"tab\"]', 'Translations').should(\n 'not.exist'\n );\n // And the original dialog title text should also be gone.\n cy.contains('[role=\"dialog\"]', englishTitle).should('not.exist');\n });\n }\n );\n\n it('should close the settings dialog with Escape', () => {\n cy.getByTestId(header.settingsTooltip).click();\n cy.get('[role=\"dialog\"]').should('be.visible');\n cy.get('body').type('{esc}');\n cy.get('[role=\"dialog\"]').should('not.exist');\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,cAAc,QAAwB;CACpD,MAAM,EAAE,WAAW,OAAO;CAC1B,MAAM,EAAE,aAAa;AAErB,UAAS,6BAA6B;AACpC,mBAAiB;AACf,MAAG,MAAM,OAAO,YAAY;AAC5B,MAAG,IAAI,OAAO,CAAC,OAAO,aAAa;IACnC;AAEF,KAAG,yDAAyD;AAC1D,MAAG,YAAY,OAAO,gBAAgB,CAAC,OAAO;AAC9C,MAAG,IAAI,oBAAkB,CAAC,OAAO,aAAa;AAE9C,MAAG,SAAS,qBAAmB,gBAAgB,CAAC,OAAO,aAAa;IACpE;AAEF,KAAG,iEAAiE;AAClE,MAAG,YAAY,OAAO,gBAAgB,CAAC,OAAO;AAC9C,MAAG,IAAI,oBAAkB,CAAC,OAAO,aAAa;AAC9C,MAAG,IAAI,mCAA+B,CAAC,OAAO,eAAe,EAAE;AAC/D,MAAG,SAAS,oCAAgC,eAAe,CAAC,OAC1D,aACD;IACD;AAEF,KAAG,kEAAkE;AACnE,MAAG,YAAY,OAAO,gBAAgB,CAAC,OAAO;AAC9C,MAAG,IAAI,oBAAkB,CAAC,OAAO,aAAa;AAC9C,MAAG,SAAS,oCAAgC,eAAe,CAAC,OAAO;AAEnE,MAAG,SACD,qBACA,sDACD,CAAC,OAAO,aAAa;AAEtB,MAAG,SAAS,qBAAmB,UAAU,CAAC,OAAO,aAAa;AAC9D,MAAG,SAAS,qBAAmB,SAAS,CAAC,OAAO,aAAa;IAC7D;AAEF,mBAAA,QAAQ,SAAS,SAAS,iBAAiB,CACzC,uEACM;AACN,MAAG,YAAY,OAAO,gBAAgB,CAAC,OAAO;AAC9C,MAAG,IAAI,oBAAkB,CAAC,OAAO,aAAa;AAC9C,MAAG,SAAS,oCAAgC,eAAe,CAAC,OAAO;AAInE,MAAG,SAAS,qBAAmB,gBAAgB,CAC5C,OAAO,OAAO,CACd,MAAM,iBAAiB;AAGtB,OAAG,SAAS,mCAAiC,SAAS,CAAC,MAAM,EAC3D,OAAO,MACR,CAAC;AAKF,OAAG,SAAS,oCAAgC,eAAe,CAAC,OAC1D,YACD;AAED,OAAG,SAAS,qBAAmB,aAAa,CAAC,OAAO,YAAY;KAChE;IAEL;AAED,KAAG,sDAAsD;AACvD,MAAG,YAAY,OAAO,gBAAgB,CAAC,OAAO;AAC9C,MAAG,IAAI,oBAAkB,CAAC,OAAO,aAAa;AAC9C,MAAG,IAAI,OAAO,CAAC,KAAK,QAAQ;AAC5B,MAAG,IAAI,oBAAkB,CAAC,OAAO,YAAY;IAC7C;GACF"}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
const require_features = require("../features.cjs");
|
|
1
2
|
//#region src/tests/signin.ts
|
|
2
3
|
function signinTests(config) {
|
|
3
|
-
const { header } = config.testIds;
|
|
4
|
+
const { header, login } = config.testIds;
|
|
4
5
|
const { loginDialog } = config.selectors;
|
|
6
|
+
const { features } = config;
|
|
5
7
|
describe("sign in / sign out", () => {
|
|
6
8
|
beforeEach(() => {
|
|
7
9
|
cy.intercept("POST", "/-/verdaccio/sec/login").as("sign");
|
|
10
|
+
cy.visit(config.registryUrl);
|
|
8
11
|
});
|
|
9
12
|
it("should login user", () => {
|
|
10
|
-
cy.visit(config.registryUrl);
|
|
11
13
|
cy.login(config.credentials.user, config.credentials.password, {
|
|
12
14
|
loginButton: header.loginButton,
|
|
13
15
|
...loginDialog
|
|
@@ -18,7 +20,6 @@ function signinTests(config) {
|
|
|
18
20
|
cy.getByTestId(header.greetingsLabel).contains(config.credentials.user);
|
|
19
21
|
});
|
|
20
22
|
it("should logout a user", () => {
|
|
21
|
-
cy.visit(config.registryUrl);
|
|
22
23
|
cy.login(config.credentials.user, config.credentials.password, {
|
|
23
24
|
loginButton: header.loginButton,
|
|
24
25
|
...loginDialog
|
|
@@ -30,6 +31,27 @@ function signinTests(config) {
|
|
|
30
31
|
cy.wait(200);
|
|
31
32
|
cy.getByTestId(header.loginButton).contains("Login");
|
|
32
33
|
});
|
|
34
|
+
require_features.maybeIt(features.signin.validationTests)("should disable the submit button while the form is empty", () => {
|
|
35
|
+
cy.getByTestId(header.loginButton).click();
|
|
36
|
+
cy.getByTestId(login.dialog).should("be.visible");
|
|
37
|
+
cy.get(loginDialog.submitButton).should("be.disabled");
|
|
38
|
+
});
|
|
39
|
+
require_features.maybeIt(features.signin.validationTests)("should keep the submit button disabled when only the username is filled", () => {
|
|
40
|
+
cy.getByTestId(header.loginButton).click();
|
|
41
|
+
cy.getByTestId(login.dialog).should("be.visible");
|
|
42
|
+
cy.get(loginDialog.usernameInput).type(config.credentials.user);
|
|
43
|
+
cy.get(loginDialog.submitButton).should("be.disabled");
|
|
44
|
+
});
|
|
45
|
+
require_features.maybeIt(features.signin.validationTests)("should show an error banner on invalid credentials", () => {
|
|
46
|
+
cy.login(config.credentials.user, "definitely-wrong-password-xyz", {
|
|
47
|
+
loginButton: header.loginButton,
|
|
48
|
+
...loginDialog
|
|
49
|
+
});
|
|
50
|
+
cy.wait("@sign").its("response.statusCode").should("not.eq", 200);
|
|
51
|
+
cy.getByTestId(login.error, { timeout: 5e3 }).should("be.visible");
|
|
52
|
+
cy.getByTestId(login.error).should("contain.text", "Invalid username or password");
|
|
53
|
+
cy.getByTestId(login.dialog).should("be.visible");
|
|
54
|
+
});
|
|
33
55
|
});
|
|
34
56
|
}
|
|
35
57
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signin.cjs","names":[],"sources":["../../../src/tests/signin.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { RegistryConfig } from '../types';\n\nexport function signinTests(config: RegistryConfig) {\n const { header } = config.testIds;\n const { loginDialog } = config.selectors;\n\n describe('sign in / sign out', () => {\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n });\n\n it('should login user', () => {\n cy.
|
|
1
|
+
{"version":3,"file":"signin.cjs","names":[],"sources":["../../../src/tests/signin.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\nimport { maybeIt } from '../features';\nimport { RegistryConfig } from '../types';\n\nexport function signinTests(config: RegistryConfig) {\n const { header, login } = config.testIds;\n const { loginDialog } = config.selectors;\n const { features } = config;\n\n describe('sign in / sign out', () => {\n beforeEach(() => {\n cy.intercept('POST', '/-/verdaccio/sec/login').as('sign');\n cy.visit(config.registryUrl);\n });\n\n it('should login user', () => {\n cy.login(config.credentials.user, config.credentials.password, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@sign');\n cy.getByTestId(header.logInDialogIcon).click();\n cy.wait(100);\n cy.getByTestId(header.greetingsLabel).contains(config.credentials.user);\n });\n\n it('should logout a user', () => {\n cy.login(config.credentials.user, config.credentials.password, {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n cy.wait('@sign');\n cy.getByTestId(header.logInDialogIcon).click();\n cy.wait(100);\n cy.getByTestId(header.logOutDialogIcon).click();\n cy.wait(200);\n cy.getByTestId(header.loginButton).contains('Login');\n });\n\n // ── Validation tests ───────────────────────────────────────────\n // The login form uses a yup schema (username >= 2 chars + URL-safe,\n // password >= 2 chars) and the submit button is `disabled={!isValid}`.\n // Server rejection renders an error banner with `data-testid=\"error\"`\n // inside `LoginDialogFormError`. Gated on\n // `features.signin.validationTests` so builds with a different\n // schema or error string can skip without forking.\n\n maybeIt(features.signin.validationTests)(\n 'should disable the submit button while the form is empty',\n () => {\n cy.getByTestId(header.loginButton).click();\n cy.getByTestId(login.dialog).should('be.visible');\n // Neither field filled → schema invalid → button disabled.\n cy.get(loginDialog.submitButton).should('be.disabled');\n }\n );\n\n maybeIt(features.signin.validationTests)(\n 'should keep the submit button disabled when only the username is filled',\n () => {\n cy.getByTestId(header.loginButton).click();\n cy.getByTestId(login.dialog).should('be.visible');\n cy.get(loginDialog.usernameInput).type(config.credentials.user);\n // Password still empty → schema still invalid.\n cy.get(loginDialog.submitButton).should('be.disabled');\n }\n );\n\n maybeIt(features.signin.validationTests)(\n 'should show an error banner on invalid credentials',\n () => {\n cy.login(config.credentials.user, 'definitely-wrong-password-xyz', {\n loginButton: header.loginButton,\n ...loginDialog,\n });\n // The request goes through (valid schema), the server rejects it.\n cy.wait('@sign').its('response.statusCode').should('not.eq', 200);\n // The form sets errors.root which renders via LoginDialogFormError\n // with a fixed English string (\"Invalid username or password\").\n cy.getByTestId(login.error, { timeout: 5000 }).should('be.visible');\n cy.getByTestId(login.error).should(\n 'contain.text',\n 'Invalid username or password'\n );\n // Dialog should still be open so the user can retry.\n cy.getByTestId(login.dialog).should('be.visible');\n }\n );\n });\n}\n"],"mappings":";;AAKA,SAAgB,YAAY,QAAwB;CAClD,MAAM,EAAE,QAAQ,UAAU,OAAO;CACjC,MAAM,EAAE,gBAAgB,OAAO;CAC/B,MAAM,EAAE,aAAa;AAErB,UAAS,4BAA4B;AACnC,mBAAiB;AACf,MAAG,UAAU,QAAQ,yBAAyB,CAAC,GAAG,OAAO;AACzD,MAAG,MAAM,OAAO,YAAY;IAC5B;AAEF,KAAG,2BAA2B;AAC5B,MAAG,MAAM,OAAO,YAAY,MAAM,OAAO,YAAY,UAAU;IAC7D,aAAa,OAAO;IACpB,GAAG;IACJ,CAAC;AACF,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,OAAO,gBAAgB,CAAC,OAAO;AAC9C,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,OAAO,eAAe,CAAC,SAAS,OAAO,YAAY,KAAK;IACvE;AAEF,KAAG,8BAA8B;AAC/B,MAAG,MAAM,OAAO,YAAY,MAAM,OAAO,YAAY,UAAU;IAC7D,aAAa,OAAO;IACpB,GAAG;IACJ,CAAC;AACF,MAAG,KAAK,QAAQ;AAChB,MAAG,YAAY,OAAO,gBAAgB,CAAC,OAAO;AAC9C,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,OAAO,iBAAiB,CAAC,OAAO;AAC/C,MAAG,KAAK,IAAI;AACZ,MAAG,YAAY,OAAO,YAAY,CAAC,SAAS,QAAQ;IACpD;AAUF,mBAAA,QAAQ,SAAS,OAAO,gBAAgB,CACtC,kEACM;AACJ,MAAG,YAAY,OAAO,YAAY,CAAC,OAAO;AAC1C,MAAG,YAAY,MAAM,OAAO,CAAC,OAAO,aAAa;AAEjD,MAAG,IAAI,YAAY,aAAa,CAAC,OAAO,cAAc;IAEzD;AAED,mBAAA,QAAQ,SAAS,OAAO,gBAAgB,CACtC,iFACM;AACJ,MAAG,YAAY,OAAO,YAAY,CAAC,OAAO;AAC1C,MAAG,YAAY,MAAM,OAAO,CAAC,OAAO,aAAa;AACjD,MAAG,IAAI,YAAY,cAAc,CAAC,KAAK,OAAO,YAAY,KAAK;AAE/D,MAAG,IAAI,YAAY,aAAa,CAAC,OAAO,cAAc;IAEzD;AAED,mBAAA,QAAQ,SAAS,OAAO,gBAAgB,CACtC,4DACM;AACJ,MAAG,MAAM,OAAO,YAAY,MAAM,iCAAiC;IACjE,aAAa,OAAO;IACpB,GAAG;IACJ,CAAC;AAEF,MAAG,KAAK,QAAQ,CAAC,IAAI,sBAAsB,CAAC,OAAO,UAAU,IAAI;AAGjE,MAAG,YAAY,MAAM,OAAO,EAAE,SAAS,KAAM,CAAC,CAAC,OAAO,aAAa;AACnE,MAAG,YAAY,MAAM,MAAM,CAAC,OAC1B,gBACA,+BACD;AAED,MAAG,YAAY,MAAM,OAAO,CAAC,OAAO,aAAa;IAEpD;GACD"}
|
|
@@ -17,6 +17,17 @@ declare global {
|
|
|
17
17
|
* Find element by data-testid attribute.
|
|
18
18
|
*/
|
|
19
19
|
getByTestId(selector: string, ...args: any[]): Chainable<JQuery<HTMLElement>>;
|
|
20
|
+
/**
|
|
21
|
+
* Find a form input whose associated <label> text matches `text`.
|
|
22
|
+
*
|
|
23
|
+
* Used by suites that target pages without stable `id`/testid
|
|
24
|
+
* selectors on their inputs (e.g. the ChangePassword form).
|
|
25
|
+
* Resolves the label's `for` attribute and returns the input
|
|
26
|
+
* it points to, so `.type(...)` / `.clear()` work directly.
|
|
27
|
+
*
|
|
28
|
+
* `text` may be a string (substring match) or a RegExp.
|
|
29
|
+
*/
|
|
30
|
+
getByLabel(text: string | RegExp): Chainable<JQuery<HTMLElement>>;
|
|
20
31
|
/**
|
|
21
32
|
* Login to Verdaccio UI. Selectors default to Verdaccio 6.x
|
|
22
33
|
* conventions; pass `selectors` to override any subset for
|
|
@@ -14,6 +14,13 @@ var DEFAULT_LOGIN_SELECTORS = {
|
|
|
14
14
|
Cypress.Commands.add("getByTestId", (selector, ...args) => {
|
|
15
15
|
return cy.get(`[data-testid=${selector}]`, ...args);
|
|
16
16
|
});
|
|
17
|
+
Cypress.Commands.add("getByLabel", (text) => {
|
|
18
|
+
return cy.contains("label", text).then(($label) => {
|
|
19
|
+
const inputId = $label.attr("for");
|
|
20
|
+
if (!inputId) throw new Error(`getByLabel: matching label has no "for" attribute (text=${String(text)})`);
|
|
21
|
+
return cy.get(`#${inputId}`);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
17
24
|
Cypress.Commands.add("login", (user, password, selectors = {}) => {
|
|
18
25
|
const loginButton = selectors.loginButton ?? DEFAULT_LOGIN_SELECTORS.loginButton;
|
|
19
26
|
const usernameInput = selectors.usernameInput ?? DEFAULT_LOGIN_SELECTORS.usernameInput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/commands/index.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\n/**\n * Default login form selectors. Kept in sync with `DEFAULT_SELECTORS`\n * in ../testIds.ts — duplicating them here (as plain constants) lets\n * `cy.login` call sites that don't pass an explicit selectors object\n * still work without importing anything.\n */\nconst DEFAULT_LOGIN_SELECTORS = {\n loginButton: 'header--button-login',\n usernameInput: '#login--dialog-username',\n passwordInput: '#login--dialog-password',\n submitButton: '#login--dialog-button-submit',\n};\n\nexport interface LoginSelectors {\n /** data-testid of the header \"Login\" button that opens the dialog. */\n loginButton?: string;\n /** CSS selector for the username input. */\n usernameInput?: string;\n /** CSS selector for the password input. */\n passwordInput?: string;\n /** CSS selector for the submit button. */\n submitButton?: string;\n}\n\ndeclare global {\n namespace Cypress {\n interface Chainable {\n /**\n * Find element by data-testid attribute.\n */\n getByTestId(selector: string, ...args: any[]): Chainable<JQuery<HTMLElement>>;\n /**\n * Login to Verdaccio UI. Selectors default to Verdaccio 6.x\n * conventions; pass `selectors` to override any subset for\n * non-default builds.\n */\n login(\n user: string,\n password: string,\n selectors?: LoginSelectors\n ): Chainable<void>;\n }\n }\n}\n\nCypress.Commands.add('getByTestId', (selector: string, ...args: any[]) => {\n return cy.get(`[data-testid=${selector}]`, ...args);\n});\n\nCypress.Commands.add(\n 'login',\n (user: string, password: string, selectors: LoginSelectors = {}) => {\n const loginButton = selectors.loginButton ?? DEFAULT_LOGIN_SELECTORS.loginButton;\n const usernameInput =\n selectors.usernameInput ?? DEFAULT_LOGIN_SELECTORS.usernameInput;\n const passwordInput =\n selectors.passwordInput ?? DEFAULT_LOGIN_SELECTORS.passwordInput;\n const submitButton =\n selectors.submitButton ?? DEFAULT_LOGIN_SELECTORS.submitButton;\n\n cy.getByTestId(loginButton).click();\n cy.wait(300);\n cy.get(usernameInput).type(user);\n cy.wait(200);\n cy.get(passwordInput).type(password);\n cy.wait(500);\n cy.get(submitButton).click();\n }\n);\n\nexport {};\n"],"mappings":";;;;;;;AAQA,IAAM,0BAA0B;CAC9B,aAAa;CACb,eAAe;CACf,eAAe;CACf,cAAc;CACf;
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/commands/index.ts"],"sourcesContent":["/// <reference types=\"cypress\" />\n\n/**\n * Default login form selectors. Kept in sync with `DEFAULT_SELECTORS`\n * in ../testIds.ts — duplicating them here (as plain constants) lets\n * `cy.login` call sites that don't pass an explicit selectors object\n * still work without importing anything.\n */\nconst DEFAULT_LOGIN_SELECTORS = {\n loginButton: 'header--button-login',\n usernameInput: '#login--dialog-username',\n passwordInput: '#login--dialog-password',\n submitButton: '#login--dialog-button-submit',\n};\n\nexport interface LoginSelectors {\n /** data-testid of the header \"Login\" button that opens the dialog. */\n loginButton?: string;\n /** CSS selector for the username input. */\n usernameInput?: string;\n /** CSS selector for the password input. */\n passwordInput?: string;\n /** CSS selector for the submit button. */\n submitButton?: string;\n}\n\ndeclare global {\n namespace Cypress {\n interface Chainable {\n /**\n * Find element by data-testid attribute.\n */\n getByTestId(selector: string, ...args: any[]): Chainable<JQuery<HTMLElement>>;\n /**\n * Find a form input whose associated <label> text matches `text`.\n *\n * Used by suites that target pages without stable `id`/testid\n * selectors on their inputs (e.g. the ChangePassword form).\n * Resolves the label's `for` attribute and returns the input\n * it points to, so `.type(...)` / `.clear()` work directly.\n *\n * `text` may be a string (substring match) or a RegExp.\n */\n getByLabel(text: string | RegExp): Chainable<JQuery<HTMLElement>>;\n /**\n * Login to Verdaccio UI. Selectors default to Verdaccio 6.x\n * conventions; pass `selectors` to override any subset for\n * non-default builds.\n */\n login(\n user: string,\n password: string,\n selectors?: LoginSelectors\n ): Chainable<void>;\n }\n }\n}\n\nCypress.Commands.add('getByTestId', (selector: string, ...args: any[]) => {\n return cy.get(`[data-testid=${selector}]`, ...args);\n});\n\nCypress.Commands.add('getByLabel', (text: string | RegExp) => {\n // Resolve the associated input via the label's `for` attribute, which\n // MUI TextField sets to the auto-generated input id. Scoping through\n // `contains()` returns the <label> element itself.\n return cy.contains('label', text).then(($label) => {\n const inputId = $label.attr('for');\n if (!inputId) {\n throw new Error(\n `getByLabel: matching label has no \"for\" attribute (text=${String(text)})`\n );\n }\n return cy.get(`#${inputId}`);\n });\n});\n\nCypress.Commands.add(\n 'login',\n (user: string, password: string, selectors: LoginSelectors = {}) => {\n const loginButton = selectors.loginButton ?? DEFAULT_LOGIN_SELECTORS.loginButton;\n const usernameInput =\n selectors.usernameInput ?? DEFAULT_LOGIN_SELECTORS.usernameInput;\n const passwordInput =\n selectors.passwordInput ?? DEFAULT_LOGIN_SELECTORS.passwordInput;\n const submitButton =\n selectors.submitButton ?? DEFAULT_LOGIN_SELECTORS.submitButton;\n\n cy.getByTestId(loginButton).click();\n cy.wait(300);\n cy.get(usernameInput).type(user);\n cy.wait(200);\n cy.get(passwordInput).type(password);\n cy.wait(500);\n cy.get(submitButton).click();\n }\n);\n\nexport {};\n"],"mappings":";;;;;;;AAQA,IAAM,0BAA0B;CAC9B,aAAa;CACb,eAAe;CACf,eAAe;CACf,cAAc;CACf;AA6CD,QAAQ,SAAS,IAAI,gBAAgB,UAAkB,GAAG,SAAgB;AACxE,QAAO,GAAG,IAAI,gBAAgB,SAAS,IAAI,GAAG,KAAK;EACnD;AAEF,QAAQ,SAAS,IAAI,eAAe,SAA0B;AAI5D,QAAO,GAAG,SAAS,SAAS,KAAK,CAAC,MAAM,WAAW;EACjD,MAAM,UAAU,OAAO,KAAK,MAAM;AAClC,MAAI,CAAC,QACH,OAAM,IAAI,MACR,2DAA2D,OAAO,KAAK,CAAC,GACzE;AAEH,SAAO,GAAG,IAAI,IAAI,UAAU;GAC5B;EACF;AAEF,QAAQ,SAAS,IACf,UACC,MAAc,UAAkB,YAA4B,EAAE,KAAK;CAClE,MAAM,cAAc,UAAU,eAAe,wBAAwB;CACrE,MAAM,gBACJ,UAAU,iBAAiB,wBAAwB;CACrD,MAAM,gBACJ,UAAU,iBAAiB,wBAAwB;CACrD,MAAM,eACJ,UAAU,gBAAgB,wBAAwB;AAEpD,IAAG,YAAY,YAAY,CAAC,OAAO;AACnC,IAAG,KAAK,IAAI;AACZ,IAAG,IAAI,cAAc,CAAC,KAAK,KAAK;AAChC,IAAG,KAAK,IAAI;AACZ,IAAG,IAAI,cAAc,CAAC,KAAK,SAAS;AACpC,IAAG,KAAK,IAAI;AACZ,IAAG,IAAI,aAAa,CAAC,OAAO;EAE/B"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/features.ts
|
|
2
|
+
/** Defaults: all flags on. */
|
|
3
|
+
var DEFAULT_FEATURES = {
|
|
4
|
+
search: {
|
|
5
|
+
resultsDropdown: true,
|
|
6
|
+
resultClickNavigation: true
|
|
7
|
+
},
|
|
8
|
+
home: { publishedPackageRendering: true },
|
|
9
|
+
settings: { languageSwitcher: true },
|
|
10
|
+
signin: { validationTests: true },
|
|
11
|
+
layout: { themeSwitch: true },
|
|
12
|
+
publish: {
|
|
13
|
+
downloadTarball: true,
|
|
14
|
+
rawViewer: true
|
|
15
|
+
},
|
|
16
|
+
changePassword: {
|
|
17
|
+
happyPath: true,
|
|
18
|
+
validation: true,
|
|
19
|
+
wrongOldPassword: true
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Merge user overrides into the default feature flags. Per-section,
|
|
24
|
+
* one level deep — matching the style of `mergeTestIds`.
|
|
25
|
+
*/
|
|
26
|
+
function mergeFeatures(defaults, overrides) {
|
|
27
|
+
if (!overrides) return defaults;
|
|
28
|
+
return {
|
|
29
|
+
search: {
|
|
30
|
+
...defaults.search,
|
|
31
|
+
...overrides.search
|
|
32
|
+
},
|
|
33
|
+
home: {
|
|
34
|
+
...defaults.home,
|
|
35
|
+
...overrides.home
|
|
36
|
+
},
|
|
37
|
+
settings: {
|
|
38
|
+
...defaults.settings,
|
|
39
|
+
...overrides.settings
|
|
40
|
+
},
|
|
41
|
+
signin: {
|
|
42
|
+
...defaults.signin,
|
|
43
|
+
...overrides.signin
|
|
44
|
+
},
|
|
45
|
+
layout: {
|
|
46
|
+
...defaults.layout,
|
|
47
|
+
...overrides.layout
|
|
48
|
+
},
|
|
49
|
+
publish: {
|
|
50
|
+
...defaults.publish,
|
|
51
|
+
...overrides.publish
|
|
52
|
+
},
|
|
53
|
+
changePassword: {
|
|
54
|
+
...defaults.changePassword,
|
|
55
|
+
...overrides.changePassword
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Helper that returns either `it` or `it.skip` depending on an
|
|
61
|
+
* enabled flag. Usage:
|
|
62
|
+
*
|
|
63
|
+
* maybeIt(features.search.resultsDropdown)('…', () => { … });
|
|
64
|
+
*/
|
|
65
|
+
function maybeIt(enabled) {
|
|
66
|
+
return enabled ? it : it.skip;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { DEFAULT_FEATURES, maybeIt, mergeFeatures };
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=features.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features.js","names":[],"sources":["../../src/features.ts"],"sourcesContent":["/**\n * Per-test feature flags for enabling/disabling individual test cases\n * in the e2e-ui suites.\n *\n * The Verdaccio UI behaves differently across branches — search result\n * payload shape differs between the `/-/v1/search` and\n * `/-/verdaccio/data/search/*` endpoints, the language picker was\n * added in a specific minor, etc. Rather than forking the suite per\n * branch, consumers can disable individual tests via\n * `createRegistryConfig({ features: { … } })`.\n *\n * Every flag defaults to `true` (test runs). Override to `false` to\n * convert the test into a Mocha `it.skip` call — the suite still\n * reports the test but marks it as pending.\n */\nexport interface Features {\n search: {\n /**\n * Whether to run the \"results dropdown renders a matching\n * package\" test. Disable on builds where the search result shape\n * or the Autocomplete rendering differs from the default assumption.\n */\n resultsDropdown: boolean;\n /**\n * Whether to run the \"clicking a result navigates to detail\" test.\n * Depends on the Autocomplete `onSelectItem` → router wiring.\n */\n resultClickNavigation: boolean;\n };\n home: {\n /**\n * Whether to run the `with a published package` sub-block inside\n * `homeTests` (publishes a throwaway package and asserts it\n * renders in the package list).\n */\n publishedPackageRendering: boolean;\n };\n settings: {\n /**\n * Whether to run the \"change language via card click\" test.\n * Depends on the `LanguageSwitch` component layout and its\n * translation sentinels (\"Translations\", \"German\"). Skip on\n * builds that lag behind the upstream translation file.\n */\n languageSwitcher: boolean;\n };\n signin: {\n /**\n * Whether to run the three validation tests (disabled submit\n * button, invalid credentials error banner). Depends on the\n * current yup schema and the hard-coded \"Invalid username or\n * password\" error string.\n */\n validationTests: boolean;\n };\n layout: {\n /**\n * Whether to run the dark/light theme switch test. Depends on\n * `web.showThemeSwitch` (defaults to true in ui-theme) and the\n * `header--button--light` / `header--button--dark` testids.\n */\n themeSwitch: boolean;\n };\n publish: {\n /**\n * Whether to run the \"tarball download button fires a GET\" test.\n * Depends on `web.showDownloadTarball` (defaults to true) and\n * the published package manifest having a valid `dist.tarball`.\n */\n downloadTarball: boolean;\n /**\n * Whether to run the \"raw viewer dialog opens + closes\" test.\n * Depends on `web.showRaw` (defaults to true).\n */\n rawViewer: boolean;\n };\n changePassword: {\n /**\n * Whether to run the happy-path test (submit valid change,\n * expect navigation to the success page, then restore the\n * original password in `after()`).\n *\n * The suite targets /-/web/change-password, which renders only\n * when the server is configured with `flags.changePassword: true`.\n * Disable on registries that do not enable the flag.\n *\n * Also disable on **published verdaccio 6.x** (all lines through\n * 6.5.0): the reset_password handler in\n * `verdaccio/build/api/web/api/user.js` ships with an inverted\n * conditional — `validatePassword(...) === false` gates the\n * `auth.changePassword(...)` call, so a *valid* new password\n * always returns HTTP 400 (`PASSWORD_VALIDATION`). The bug is\n * fixed on the development branch but has not been released\n * in any 6.x tag, so the happy path cannot succeed against an\n * `npm install verdaccio@6` runtime.\n */\n happyPath: boolean;\n /**\n * Whether to run the client-side validation tests (submit button\n * stays disabled while fields are empty / mismatched confirm).\n * Depends on the yup `changePasswordSchema`.\n */\n validation: boolean;\n /**\n * Whether to run the \"wrong old password shows error banner\" test.\n * Depends on the server rejecting the call and the onSubmit catch\n * block surfacing `\"Failed to change password\"` via\n * `LoginDialogFormError`.\n */\n wrongOldPassword: boolean;\n };\n}\n\n/** Defaults: all flags on. */\nexport const DEFAULT_FEATURES: Features = {\n search: {\n resultsDropdown: true,\n resultClickNavigation: true,\n },\n home: {\n publishedPackageRendering: true,\n },\n settings: {\n languageSwitcher: true,\n },\n signin: {\n validationTests: true,\n },\n layout: {\n themeSwitch: true,\n },\n publish: {\n downloadTarball: true,\n rawViewer: true,\n },\n changePassword: {\n happyPath: true,\n validation: true,\n wrongOldPassword: true,\n },\n};\n\nimport type { DeepPartial } from './testIds';\n\n/**\n * Merge user overrides into the default feature flags. Per-section,\n * one level deep — matching the style of `mergeTestIds`.\n */\nexport function mergeFeatures(\n defaults: Features,\n overrides?: DeepPartial<Features>\n): Features {\n if (!overrides) return defaults;\n return {\n search: { ...defaults.search, ...overrides.search },\n home: { ...defaults.home, ...overrides.home },\n settings: { ...defaults.settings, ...overrides.settings },\n signin: { ...defaults.signin, ...overrides.signin },\n layout: { ...defaults.layout, ...overrides.layout },\n publish: { ...defaults.publish, ...overrides.publish },\n changePassword: { ...defaults.changePassword, ...overrides.changePassword },\n };\n}\n\n/**\n * Helper that returns either `it` or `it.skip` depending on an\n * enabled flag. Usage:\n *\n * maybeIt(features.search.resultsDropdown)('…', () => { … });\n */\nexport function maybeIt(enabled: boolean): Mocha.TestFunction | Mocha.PendingTestFunction {\n return enabled ? it : it.skip;\n}\n"],"mappings":";;AAkHA,IAAa,mBAA6B;CACxC,QAAQ;EACN,iBAAiB;EACjB,uBAAuB;EACxB;CACD,MAAM,EACJ,2BAA2B,MAC5B;CACD,UAAU,EACR,kBAAkB,MACnB;CACD,QAAQ,EACN,iBAAiB,MAClB;CACD,QAAQ,EACN,aAAa,MACd;CACD,SAAS;EACP,iBAAiB;EACjB,WAAW;EACZ;CACD,gBAAgB;EACd,WAAW;EACX,YAAY;EACZ,kBAAkB;EACnB;CACF;;;;;AAQD,SAAgB,cACd,UACA,WACU;AACV,KAAI,CAAC,UAAW,QAAO;AACvB,QAAO;EACL,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;GAAQ;EACnD,MAAM;GAAE,GAAG,SAAS;GAAM,GAAG,UAAU;GAAM;EAC7C,UAAU;GAAE,GAAG,SAAS;GAAU,GAAG,UAAU;GAAU;EACzD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;GAAQ;EACnD,QAAQ;GAAE,GAAG,SAAS;GAAQ,GAAG,UAAU;GAAQ;EACnD,SAAS;GAAE,GAAG,SAAS;GAAS,GAAG,UAAU;GAAS;EACtD,gBAAgB;GAAE,GAAG,SAAS;GAAgB,GAAG,UAAU;GAAgB;EAC5E;;;;;;;;AASH,SAAgB,QAAQ,SAAkE;AACxF,QAAO,UAAU,KAAK,GAAG"}
|
package/build/esm/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cleanupPublished, publishPackage, unpublishPackage } from "./tasks/publish.js";
|
|
2
|
+
import { DEFAULT_FEATURES, maybeIt, mergeFeatures } from "./features.js";
|
|
2
3
|
import { DEFAULT_SELECTORS, DEFAULT_TEST_IDS, mergeSelectors, mergeTestIds } from "./testIds.js";
|
|
3
4
|
import { homeTests } from "./tests/home.js";
|
|
4
5
|
import { signinTests } from "./tests/signin.js";
|
|
@@ -6,6 +7,7 @@ import { publishTests } from "./tests/publish.js";
|
|
|
6
7
|
import { searchTests } from "./tests/search.js";
|
|
7
8
|
import { settingsTests } from "./tests/settings.js";
|
|
8
9
|
import { layoutTests } from "./tests/layout.js";
|
|
10
|
+
import { changePasswordTests } from "./tests/change-password.js";
|
|
9
11
|
//#region src/index.ts
|
|
10
12
|
/**
|
|
11
13
|
* Build a full RegistryConfig from user-provided options with defaults.
|
|
@@ -32,7 +34,8 @@ function createRegistryConfig(options) {
|
|
|
32
34
|
},
|
|
33
35
|
title: options.title ?? "Verdaccio",
|
|
34
36
|
testIds: mergeTestIds(DEFAULT_TEST_IDS, options.testIds),
|
|
35
|
-
selectors: mergeSelectors(DEFAULT_SELECTORS, options.selectors)
|
|
37
|
+
selectors: mergeSelectors(DEFAULT_SELECTORS, options.selectors),
|
|
38
|
+
features: mergeFeatures(DEFAULT_FEATURES, options.features)
|
|
36
39
|
};
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
@@ -107,8 +110,9 @@ function registerAllTests(config) {
|
|
|
107
110
|
searchTests(config);
|
|
108
111
|
settingsTests(config);
|
|
109
112
|
publishTests(config);
|
|
113
|
+
changePasswordTests(config);
|
|
110
114
|
}
|
|
111
115
|
//#endregion
|
|
112
|
-
export { DEFAULT_SELECTORS, DEFAULT_TEST_IDS, cleanupPublished, createRegistryConfig, homeTests, layoutTests, publishPackage, publishTests, registerAllTests, searchTests, settingsTests, setupVerdaccioTasks, signinTests, unpublishPackage };
|
|
116
|
+
export { DEFAULT_FEATURES, DEFAULT_SELECTORS, DEFAULT_TEST_IDS, changePasswordTests, cleanupPublished, createRegistryConfig, homeTests, layoutTests, maybeIt, publishPackage, publishTests, registerAllTests, searchTests, settingsTests, setupVerdaccioTasks, signinTests, unpublishPackage };
|
|
113
117
|
|
|
114
118
|
//# sourceMappingURL=index.js.map
|
package/build/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import {\n cleanupPublished,\n publishPackage,\n PublishPackageResult,\n PublishPackageTaskInput,\n unpublishPackage,\n UnpublishPackageInput,\n UnpublishPackageResult,\n} from './tasks';\nimport {\n DEFAULT_SELECTORS,\n DEFAULT_TEST_IDS,\n mergeSelectors,\n mergeTestIds,\n} from './testIds';\nimport { RegistryConfig, RegistryTaskResult, VerdaccioUiOptions } from './types';\n\nexport type {\n RegistryConfig,\n RegistryTaskResult,\n VerdaccioUiOptions,\n} from './types';\nexport type {\n PublishPackageInput,\n PublishPackageTaskInput,\n PublishPackageResult,\n UnpublishPackageInput,\n UnpublishPackageResult,\n} from './tasks';\nexport type { DeepPartial, Selectors, TestIds } from './testIds';\nexport { DEFAULT_SELECTORS, DEFAULT_TEST_IDS } from './testIds';\nexport { publishPackage, cleanupPublished, unpublishPackage } from './tasks';\nexport {\n homeTests,\n signinTests,\n publishTests,\n searchTests,\n settingsTests,\n layoutTests,\n} from './tests';\n\n/**\n * Build a full RegistryConfig from user-provided options with defaults.\n *\n * `testIds` and `selectors` are deep-merged per section with the\n * defaults in `./testIds`. Consumers targeting a non-default Verdaccio\n * build can override just the fields that drifted:\n *\n * createRegistryConfig({\n * registryUrl: 'http://localhost:4873',\n * testIds: {\n * header: { settingsTooltip: 'my-new-settings-btn' },\n * },\n * });\n */\nexport function createRegistryConfig(options: VerdaccioUiOptions): RegistryConfig {\n const url = new URL(options.registryUrl);\n return {\n registryUrl: options.registryUrl,\n port: options.port ?? (parseInt(url.port, 10) || 4873),\n credentials: options.credentials ?? { user: 'test', password: 'test' },\n title: options.title ?? 'Verdaccio',\n testIds: mergeTestIds(DEFAULT_TEST_IDS, options.testIds),\n selectors: mergeSelectors(DEFAULT_SELECTORS, options.selectors),\n };\n}\n\n/**\n * Register Verdaccio Cypress tasks in setupNodeEvents.\n *\n * Usage in cypress.config.ts:\n * import { setupVerdaccioTasks } from '@verdaccio/e2e-ui';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * setupVerdaccioTasks(on, { registryUrl: 'http://localhost:4873' });\n * },\n * },\n * });\n */\nexport function setupVerdaccioTasks(\n on: Cypress.PluginEvents,\n options: VerdaccioUiOptions\n): void {\n const config = createRegistryConfig(options);\n\n on('task', {\n registry(): RegistryTaskResult {\n return {\n registryUrl: config.registryUrl,\n port: config.port,\n };\n },\n /**\n * Publish a throwaway package to the registry.\n *\n * Usage from a Cypress spec:\n *\n * cy.task('publishPackage', { pkgName: '@verdaccio/pkg-scoped' });\n *\n * Any field not provided falls back to the values configured here\n * (registryUrl, credentials). The task returns a PublishPackageResult.\n */\n async publishPackage(\n input: PublishPackageTaskInput\n ): Promise<PublishPackageResult> {\n return publishPackage({\n registryUrl: input.registryUrl ?? config.registryUrl,\n credentials: input.credentials ?? config.credentials,\n pkgName: input.pkgName,\n version: input.version,\n dependencies: input.dependencies,\n devDependencies: input.devDependencies,\n unique: input.unique,\n });\n },\n /**\n * Remove a temp project folder returned by a previous publishPackage\n * call. Returns null (Cypress tasks must return something serializable).\n *\n * cy.task('cleanupPublished', result.tempFolder);\n */\n async cleanupPublished(tempFolder: string): Promise<null> {\n await cleanupPublished(tempFolder);\n return null;\n },\n /**\n * Unpublish a package from the registry so the next test starts\n * from a clean slate. Accepts either a package name or a full input\n * object (to reuse a prior tempFolder's token).\n *\n * cy.task('unpublishPackage', '@verdaccio/pkg-scoped');\n * cy.task('unpublishPackage', { pkgName, tempFolder });\n */\n async unpublishPackage(\n input: string | (Omit<UnpublishPackageInput, 'registryUrl'> & {\n registryUrl?: string;\n })\n ): Promise<UnpublishPackageResult> {\n const normalized =\n typeof input === 'string'\n ? { pkgName: input, registryUrl: config.registryUrl }\n : { ...input, registryUrl: input.registryUrl ?? config.registryUrl };\n return unpublishPackage(normalized);\n },\n });\n}\n\n/**\n * Register all Verdaccio UI tests.\n *\n * Usage in a spec file:\n * import { registerAllTests, createRegistryConfig } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * registerAllTests(config);\n *\n * Or pick individual suites:\n * import { homeTests, signinTests } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * homeTests(config);\n * signinTests(config);\n */\nexport function registerAllTests(config: RegistryConfig): void {\n homeTests(config);\n signinTests(config);\n layoutTests(config);\n searchTests(config);\n settingsTests(config);\n publishTests(config);\n}\n\n// Re-export for convenience\nimport {\n homeTests,\n signinTests,\n publishTests,\n searchTests,\n settingsTests,\n layoutTests,\n} from './tests';\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import {\n cleanupPublished,\n publishPackage,\n PublishPackageResult,\n PublishPackageTaskInput,\n unpublishPackage,\n UnpublishPackageInput,\n UnpublishPackageResult,\n} from './tasks';\nimport { DEFAULT_FEATURES, mergeFeatures } from './features';\nimport {\n DEFAULT_SELECTORS,\n DEFAULT_TEST_IDS,\n mergeSelectors,\n mergeTestIds,\n} from './testIds';\nimport { RegistryConfig, RegistryTaskResult, VerdaccioUiOptions } from './types';\n\nexport type {\n RegistryConfig,\n RegistryTaskResult,\n VerdaccioUiOptions,\n} from './types';\nexport type {\n PublishPackageInput,\n PublishPackageTaskInput,\n PublishPackageResult,\n UnpublishPackageInput,\n UnpublishPackageResult,\n} from './tasks';\nexport type { DeepPartial, Selectors, TestIds } from './testIds';\nexport type { Features } from './features';\nexport { DEFAULT_SELECTORS, DEFAULT_TEST_IDS } from './testIds';\nexport { DEFAULT_FEATURES, maybeIt } from './features';\nexport { publishPackage, cleanupPublished, unpublishPackage } from './tasks';\nexport {\n homeTests,\n signinTests,\n publishTests,\n searchTests,\n settingsTests,\n layoutTests,\n changePasswordTests,\n} from './tests';\n\n/**\n * Build a full RegistryConfig from user-provided options with defaults.\n *\n * `testIds` and `selectors` are deep-merged per section with the\n * defaults in `./testIds`. Consumers targeting a non-default Verdaccio\n * build can override just the fields that drifted:\n *\n * createRegistryConfig({\n * registryUrl: 'http://localhost:4873',\n * testIds: {\n * header: { settingsTooltip: 'my-new-settings-btn' },\n * },\n * });\n */\nexport function createRegistryConfig(options: VerdaccioUiOptions): RegistryConfig {\n const url = new URL(options.registryUrl);\n return {\n registryUrl: options.registryUrl,\n port: options.port ?? (parseInt(url.port, 10) || 4873),\n credentials: options.credentials ?? { user: 'test', password: 'test' },\n title: options.title ?? 'Verdaccio',\n testIds: mergeTestIds(DEFAULT_TEST_IDS, options.testIds),\n selectors: mergeSelectors(DEFAULT_SELECTORS, options.selectors),\n features: mergeFeatures(DEFAULT_FEATURES, options.features),\n };\n}\n\n/**\n * Register Verdaccio Cypress tasks in setupNodeEvents.\n *\n * Usage in cypress.config.ts:\n * import { setupVerdaccioTasks } from '@verdaccio/e2e-ui';\n *\n * export default defineConfig({\n * e2e: {\n * setupNodeEvents(on) {\n * setupVerdaccioTasks(on, { registryUrl: 'http://localhost:4873' });\n * },\n * },\n * });\n */\nexport function setupVerdaccioTasks(\n on: Cypress.PluginEvents,\n options: VerdaccioUiOptions\n): void {\n const config = createRegistryConfig(options);\n\n on('task', {\n registry(): RegistryTaskResult {\n return {\n registryUrl: config.registryUrl,\n port: config.port,\n };\n },\n /**\n * Publish a throwaway package to the registry.\n *\n * Usage from a Cypress spec:\n *\n * cy.task('publishPackage', { pkgName: '@verdaccio/pkg-scoped' });\n *\n * Any field not provided falls back to the values configured here\n * (registryUrl, credentials). The task returns a PublishPackageResult.\n */\n async publishPackage(\n input: PublishPackageTaskInput\n ): Promise<PublishPackageResult> {\n return publishPackage({\n registryUrl: input.registryUrl ?? config.registryUrl,\n credentials: input.credentials ?? config.credentials,\n pkgName: input.pkgName,\n version: input.version,\n dependencies: input.dependencies,\n devDependencies: input.devDependencies,\n unique: input.unique,\n });\n },\n /**\n * Remove a temp project folder returned by a previous publishPackage\n * call. Returns null (Cypress tasks must return something serializable).\n *\n * cy.task('cleanupPublished', result.tempFolder);\n */\n async cleanupPublished(tempFolder: string): Promise<null> {\n await cleanupPublished(tempFolder);\n return null;\n },\n /**\n * Unpublish a package from the registry so the next test starts\n * from a clean slate. Accepts either a package name or a full input\n * object (to reuse a prior tempFolder's token).\n *\n * cy.task('unpublishPackage', '@verdaccio/pkg-scoped');\n * cy.task('unpublishPackage', { pkgName, tempFolder });\n */\n async unpublishPackage(\n input: string | (Omit<UnpublishPackageInput, 'registryUrl'> & {\n registryUrl?: string;\n })\n ): Promise<UnpublishPackageResult> {\n const normalized =\n typeof input === 'string'\n ? { pkgName: input, registryUrl: config.registryUrl }\n : { ...input, registryUrl: input.registryUrl ?? config.registryUrl };\n return unpublishPackage(normalized);\n },\n });\n}\n\n/**\n * Register all Verdaccio UI tests.\n *\n * Usage in a spec file:\n * import { registerAllTests, createRegistryConfig } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * registerAllTests(config);\n *\n * Or pick individual suites:\n * import { homeTests, signinTests } from '@verdaccio/e2e-ui';\n *\n * const config = createRegistryConfig({ registryUrl: 'http://localhost:4873' });\n * homeTests(config);\n * signinTests(config);\n */\nexport function registerAllTests(config: RegistryConfig): void {\n homeTests(config);\n signinTests(config);\n layoutTests(config);\n searchTests(config);\n settingsTests(config);\n publishTests(config);\n changePasswordTests(config);\n}\n\n// Re-export for convenience\nimport {\n homeTests,\n signinTests,\n publishTests,\n searchTests,\n settingsTests,\n layoutTests,\n changePasswordTests,\n} from './tests';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,SAAgB,qBAAqB,SAA6C;CAChF,MAAM,MAAM,IAAI,IAAI,QAAQ,YAAY;AACxC,QAAO;EACL,aAAa,QAAQ;EACrB,MAAM,QAAQ,SAAS,SAAS,IAAI,MAAM,GAAG,IAAI;EACjD,aAAa,QAAQ,eAAe;GAAE,MAAM;GAAQ,UAAU;GAAQ;EACtE,OAAO,QAAQ,SAAS;EACxB,SAAS,aAAa,kBAAkB,QAAQ,QAAQ;EACxD,WAAW,eAAe,mBAAmB,QAAQ,UAAU;EAC/D,UAAU,cAAc,kBAAkB,QAAQ,SAAS;EAC5D;;;;;;;;;;;;;;;;AAiBH,SAAgB,oBACd,IACA,SACM;CACN,MAAM,SAAS,qBAAqB,QAAQ;AAE5C,IAAG,QAAQ;EACT,WAA+B;AAC7B,UAAO;IACL,aAAa,OAAO;IACpB,MAAM,OAAO;IACd;;EAYH,MAAM,eACJ,OAC+B;AAC/B,UAAO,eAAe;IACpB,aAAa,MAAM,eAAe,OAAO;IACzC,aAAa,MAAM,eAAe,OAAO;IACzC,SAAS,MAAM;IACf,SAAS,MAAM;IACf,cAAc,MAAM;IACpB,iBAAiB,MAAM;IACvB,QAAQ,MAAM;IACf,CAAC;;EAQJ,MAAM,iBAAiB,YAAmC;AACxD,SAAM,iBAAiB,WAAW;AAClC,UAAO;;EAUT,MAAM,iBACJ,OAGiC;AAKjC,UAAO,iBAHL,OAAO,UAAU,WACb;IAAE,SAAS;IAAO,aAAa,OAAO;IAAa,GACnD;IAAE,GAAG;IAAO,aAAa,MAAM,eAAe,OAAO;IAAa,CACrC;;EAEtC,CAAC;;;;;;;;;;;;;;;;;;AAmBJ,SAAgB,iBAAiB,QAA8B;AAC7D,WAAU,OAAO;AACjB,aAAY,OAAO;AACnB,aAAY,OAAO;AACnB,aAAY,OAAO;AACnB,eAAc,OAAO;AACrB,cAAa,OAAO;AACpB,qBAAoB,OAAO"}
|
package/build/esm/testIds.js
CHANGED
|
@@ -18,6 +18,8 @@ var DEFAULT_TEST_IDS = {
|
|
|
18
18
|
loginButton: "header--button-login",
|
|
19
19
|
settingsTooltip: "header--tooltip-settings",
|
|
20
20
|
infoTooltip: "header--tooltip-info",
|
|
21
|
+
themeSwitchLight: "header--button--light",
|
|
22
|
+
themeSwitchDark: "header--button--dark",
|
|
21
23
|
logInDialogIcon: "logInDialogIcon",
|
|
22
24
|
logOutDialogIcon: "logOutDialogIcon",
|
|
23
25
|
greetingsLabel: "greetings-label"
|
|
@@ -26,6 +28,11 @@ var DEFAULT_TEST_IDS = {
|
|
|
26
28
|
container: "footer",
|
|
27
29
|
version: "version-footer"
|
|
28
30
|
},
|
|
31
|
+
login: {
|
|
32
|
+
dialog: "login--dialog",
|
|
33
|
+
dialogContent: "dialogContentLogin",
|
|
34
|
+
error: "error"
|
|
35
|
+
},
|
|
29
36
|
package: {
|
|
30
37
|
itemList: "package-item-list",
|
|
31
38
|
title: "package-title",
|
|
@@ -41,7 +48,11 @@ var DEFAULT_TEST_IDS = {
|
|
|
41
48
|
versionsTab: "versions-tab",
|
|
42
49
|
tagLatest: "tag-latest",
|
|
43
50
|
uplinksTab: "uplinks-tab",
|
|
44
|
-
noUplinks: "no-uplinks"
|
|
51
|
+
noUplinks: "no-uplinks",
|
|
52
|
+
downloadTarballBtn: "download-tarball-btn",
|
|
53
|
+
rawBtn: "raw-btn",
|
|
54
|
+
rawViewerDialog: "rawViewer--dialog",
|
|
55
|
+
closeRawViewer: "close-raw-viewer"
|
|
45
56
|
}
|
|
46
57
|
};
|
|
47
58
|
/**
|
|
@@ -78,6 +89,10 @@ function mergeTestIds(defaults, overrides) {
|
|
|
78
89
|
...defaults.footer,
|
|
79
90
|
...overrides.footer
|
|
80
91
|
},
|
|
92
|
+
login: {
|
|
93
|
+
...defaults.login,
|
|
94
|
+
...overrides.login
|
|
95
|
+
},
|
|
81
96
|
package: {
|
|
82
97
|
...defaults.package,
|
|
83
98
|
...overrides.package
|