graphdb-workbench-tests 4.0.0-TR2 → 4.0.0-TR4

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.
Files changed (33) hide show
  1. package/e2e-flaky/import/import-server-files-operations.spec.js +9 -9
  2. package/e2e-flaky/import/import-user-data-batch-operations.spec.js +7 -7
  3. package/e2e-flaky/import/import-user-data-url.spec.js +1 -1
  4. package/e2e-legacy/cluster/cluster-configuration/cluster-configuration-multi-region.spec.js +20 -0
  5. package/e2e-legacy/guides/import-rdf-file/confirm-duplicate-rdf-file.spec.js +1 -1
  6. package/e2e-legacy/guides/main-menu/main-menu-guide.spec.js +6 -14
  7. package/e2e-legacy/guides/ttyg/configure-agent/configure-agent-guide.spec.js +26 -30
  8. package/e2e-legacy/guides/ttyg/edit-agent/edit-ttyg-agent-guide.spec.js +12 -20
  9. package/e2e-legacy/import/import-server-files-batch-operations.spec.js +5 -5
  10. package/e2e-legacy/import/import-server-files.spec.js +23 -23
  11. package/e2e-legacy/import/import-user-data-file-upload.spec.js +21 -21
  12. package/e2e-legacy/import/import-user-data-text-snippet.spec.js +8 -8
  13. package/e2e-legacy/import/import-user-data-url.spec.js +3 -3
  14. package/e2e-legacy/import/import-view.spec.js +2 -2
  15. package/e2e-legacy/repository/repositories.spec.js +3 -3
  16. package/e2e-legacy/setup/users-and-access/user-and-access.spec.js +18 -19
  17. package/e2e-legacy/sparql-editor/actions/explain-query-plan.spec.js +174 -0
  18. package/e2e-legacy/ttyg/create-agent.spec.js +6 -4
  19. package/e2e-legacy/ttyg/edit-agent.spec.js +57 -0
  20. package/e2e-legacy/ttyg/ttyg-initial-state-with-configured-api-key.spec.js +1 -1
  21. package/e2e-legacy/ttyg/ttyg-initial-state-with-selected-repository.spec.js +1 -1
  22. package/e2e-legacy/ttyg/ttyg-initial-state-without-repositories.spec.js +1 -1
  23. package/fixtures/cluster/3-nodes-cluster-group-status-no-leader.json +41 -0
  24. package/npm-shrinkwrap.json +2 -2
  25. package/package.json +1 -1
  26. package/steps/cluster/cluster-configuration-steps.js +4 -0
  27. package/steps/import/import-steps.js +26 -6
  28. package/steps/main-menu-steps.js +5 -14
  29. package/steps/setup/user-and-access-steps.js +18 -14
  30. package/steps/yasgui/yasqe-steps.js +60 -0
  31. package/steps/yasgui/yasr-steps.js +15 -0
  32. package/stubs/cluster/cluster-stubs.js +8 -1
  33. package/stubs/yasgui/query-stubs.js +19 -0
@@ -39,7 +39,7 @@ describe('TTYG initial state with API key', () => {
39
39
  it('Should display the correct initial state when navigating via the navigation menu', () => {
40
40
  // Given, I visit the TTYG page via the navigation menu
41
41
  HomeSteps.visit();
42
- MainMenuSteps.clickOnTTYG();
42
+ MainMenuSteps.clickOnMenuTTYG();
43
43
  // Then,
44
44
  verifyStateWithApiKey();
45
45
  });
@@ -38,7 +38,7 @@ describe.skip('TTYG initial state with selected repository', () => {
38
38
  it('Should display the correct initial state when navigating via the navigation menu', () => {
39
39
  // Given, I visit the TTYG page via the navigation menu with a repository selected
40
40
  HomeSteps.visit();
41
- MainMenuSteps.clickOnTTYG();
41
+ MainMenuSteps.clickOnMenuTTYG();
42
42
  // Then,
43
43
  verifyStateWithSelectedRepository();
44
44
  });
@@ -14,7 +14,7 @@ describe('TTYG initial state without selected repository', () => {
14
14
  it('Should display the correct initial state when navigating via the navigation menu', () => {
15
15
  // Given, I visit the TTYG page via the navigation menu without a repository selected
16
16
  HomeSteps.visit();
17
- MainMenuSteps.clickOnTTYG();
17
+ MainMenuSteps.clickOnMenuTTYG();
18
18
  // Then,
19
19
  ErrorSteps.verifyNoConnectedRepoMessage();
20
20
  });
@@ -0,0 +1,41 @@
1
+ [
2
+ {
3
+ "address": "pc-desktop:7300",
4
+ "nodeState": "FOLLOWER",
5
+ "term": 2,
6
+ "syncStatus": {},
7
+ "lastLogTerm": 0,
8
+ "lastLogIndex": 0,
9
+ "endpoint": "http://pc-desktop:7200",
10
+ "recoveryStatus": {},
11
+ "topologyStatus": {
12
+ "state": "PRIMARY_NODE"
13
+ }
14
+ },
15
+ {
16
+ "address": "pc-desktop:7301",
17
+ "nodeState": "FOLLOWER",
18
+ "term": 2,
19
+ "syncStatus": {},
20
+ "lastLogTerm": 0,
21
+ "lastLogIndex": 0,
22
+ "endpoint": "http://pc-desktop:7201",
23
+ "recoveryStatus": {},
24
+ "topologyStatus": {
25
+ "state": "PRIMARY_NODE"
26
+ }
27
+ },
28
+ {
29
+ "address": "pc-desktop:7302",
30
+ "nodeState": "FOLLOWER",
31
+ "term": 2,
32
+ "syncStatus": {},
33
+ "lastLogTerm": 0,
34
+ "lastLogIndex": 0,
35
+ "endpoint": "http://pc-desktop:7202",
36
+ "recoveryStatus": {},
37
+ "topologyStatus": {
38
+ "state": "PRIMARY_NODE"
39
+ }
40
+ }
41
+ ]
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "graphdb-workbench-tests",
3
- "version": "4.0.0-TR2",
3
+ "version": "4.0.0-TR4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "graphdb-workbench-tests",
9
- "version": "4.0.0-TR2",
9
+ "version": "4.0.0-TR4",
10
10
  "license": "Apache-2.0",
11
11
  "devDependencies": {
12
12
  "@bahmutov/cypress-code-coverage": "^2.7.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphdb-workbench-tests",
3
- "version": "4.0.0-TR2",
3
+ "version": "4.0.0-TR4",
4
4
  "description": "Cypress tests for GraphDB workbench",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -119,6 +119,10 @@ export class ClusterConfigurationSteps {
119
119
  return this.getMultiRegionTabContent().find('.title');
120
120
  }
121
121
 
122
+ static getNoLeaderAlert() {
123
+ return this.getMultiRegionTabContent().find('.no-leader-alert');
124
+ }
125
+
122
126
  static getAddTagButton() {
123
127
  return this.getMultiRegionTabContent().find('.create-tag-btn');
124
128
  }
@@ -161,17 +161,29 @@ class ImportSteps {
161
161
  return this.getSelectedResources().eq(index).closest('.title-row');
162
162
  }
163
163
 
164
- static getResource(index) {
164
+ static getResourceTitleRow(index) {
165
165
  return this.getResources().eq(index);
166
166
  }
167
167
 
168
+ static getResourceByIndex(index) {
169
+ return this.getResourcesTable().find(`[data-test="resource-${index}"]`);
170
+ }
171
+
172
+ static getResourceNameByIndex(index) {
173
+ return this.getResourceByIndex(index).find('.cell-title').invoke('text');
174
+ }
175
+
176
+ static getResourceStatusByIndex(index) {
177
+ return this.getResourceByIndex(index).find('.import-resource-message');
178
+ }
179
+
168
180
  static getResourceByName(name) {
169
181
  return this.getResources().find('.cell-title').contains(name)
170
182
  .closest('.row.title-row');
171
183
  }
172
184
 
173
185
  static selectFileByIndex(index) {
174
- this.getResource(index).find('.select-checkbox').click();
186
+ this.getResourceTitleRow(index).find('.select-checkbox').click();
175
187
  }
176
188
 
177
189
  static selectFileByName(name) {
@@ -194,8 +206,16 @@ class ImportSteps {
194
206
  return this.getResources().find('.import-resource-message');
195
207
  }
196
208
 
197
- static checkImportedResource(index, resourceName, expectedStatus) {
209
+ static checkImportedResourceByIndex(index, resourceName, expectedStatus) {
198
210
  const status = expectedStatus || 'Imported successfully';
211
+
212
+ this.getResourceNameByIndex(index).should('contain', resourceName);
213
+ this.getResourceStatusByIndex(index).should('contain', status);
214
+ }
215
+
216
+ static checkImportedResourceByName(resourceName, expectedStatus) {
217
+ const status = expectedStatus || 'Imported successfully';
218
+
199
219
  this.getResourceByName(resourceName).should('contain', resourceName);
200
220
  this.getResourceStatus(resourceName).should('contain', status);
201
221
  }
@@ -263,11 +283,11 @@ class ImportSteps {
263
283
  }
264
284
 
265
285
  static deleteUploadedFile(index) {
266
- this.getResource(index).find('.import-resource-action-remove-btn').invoke('show').trigger('click');
286
+ this.getResourceTitleRow(index).find('.import-resource-action-remove-btn').invoke('show').trigger('click');
267
287
  }
268
288
 
269
289
  static importFile(index) {
270
- this.getResource(index).find('.import-resource-action-import-btn').invoke('show').trigger('click');
290
+ this.getResourceTitleRow(index).find('.import-resource-action-import-btn').invoke('show').trigger('click');
271
291
  }
272
292
 
273
293
  static importResourceByName(name) {
@@ -275,7 +295,7 @@ class ImportSteps {
275
295
  }
276
296
 
277
297
  static resetFileStatus(index) {
278
- this.getResource(index).find('.import-resource-action-reset-btn').click();
298
+ this.getResourceTitleRow(index).find('.import-resource-action-reset-btn').click();
279
299
  }
280
300
 
281
301
  static resetResourceStatusByName(name) {
@@ -272,23 +272,14 @@ export class MainMenuSteps {
272
272
  }
273
273
 
274
274
  // --------------------------
275
- // -- Lab menu --
275
+ // -- TTYG menu --
276
276
  // --------------------------
277
- static getMenuLab() {
278
- return this.getMainMenu().getByTestId('menu-lab');
277
+ static getMenuTTYG() {
278
+ return this.getMainMenu().getByTestId('menu-ttyg');
279
279
  }
280
280
 
281
- static clickOnMenuLab() {
282
- return this.getMenuLab().click();
283
- }
284
-
285
- static clickOnTTYG() {
286
- this.clickOnMenuLab();
287
- this.clickOnTTYGSubmenu()
288
- }
289
-
290
- static clickOnTTYGSubmenu() {
291
- this.getSubMenuButton('sub-menu-ttyg').click();
281
+ static clickOnMenuTTYG() {
282
+ return this.getMenuTTYG().click();
292
283
  }
293
284
 
294
285
  // --------------------------
@@ -1,5 +1,7 @@
1
1
  import {EnvironmentStubs} from "../../stubs/environment-stubs";
2
2
 
3
+ const CENTER_SCROLL = {scrollBehavior: 'center'};
4
+
3
5
  export class UserAndAccessSteps {
4
6
  static visit() {
5
7
  cy.visit('/users');
@@ -118,7 +120,9 @@ export class UserAndAccessSteps {
118
120
  }
119
121
 
120
122
  static typeUsername(text) {
121
- this.getUsernameField().type(text);
123
+ this.getUsernameField()
124
+ .focus()
125
+ .type(text);
122
126
  }
123
127
 
124
128
  static getPasswordField() {
@@ -238,13 +242,13 @@ export class UserAndAccessSteps {
238
242
  .contains('Global (any data repository)')
239
243
  .parent('tr')
240
244
  .find('.graphql')
241
- .realClick();
245
+ .click(CENTER_SCROLL);
242
246
  }
243
247
 
244
248
  static clickGraphqlAccessRepo(repoName) {
245
249
  this.getRepositoryRightsLine(repoName)
246
250
  .find('.graphql')
247
- .realClick();
251
+ .click(CENTER_SCROLL);
248
252
  }
249
253
 
250
254
  static clickReadAccessAny() {
@@ -252,7 +256,7 @@ export class UserAndAccessSteps {
252
256
  .contains('Global (any data repository)')
253
257
  .parent('tr')
254
258
  .find('.read')
255
- .realClick();
259
+ .click(CENTER_SCROLL);
256
260
  }
257
261
 
258
262
  static clickReadAccessRepo(repoName) {
@@ -260,7 +264,7 @@ export class UserAndAccessSteps {
260
264
  .contains(repoName)
261
265
  .parent('tr')
262
266
  .find('.read')
263
- .realClick();
267
+ .click(CENTER_SCROLL);
264
268
  }
265
269
 
266
270
  static clickWriteAccessAny() {
@@ -268,7 +272,7 @@ export class UserAndAccessSteps {
268
272
  .contains('Global (any data repository)')
269
273
  .parent('tr')
270
274
  .find('.write')
271
- .realClick();
275
+ .click(CENTER_SCROLL);
272
276
  }
273
277
 
274
278
  static clickWriteAccessRepo(repoName) {
@@ -276,7 +280,7 @@ export class UserAndAccessSteps {
276
280
  .contains(repoName)
277
281
  .parent('tr')
278
282
  .find('.write')
279
- .realClick();
283
+ .click(CENTER_SCROLL);
280
284
  }
281
285
 
282
286
  static findUserRowAlias(username, aliasName = 'userRow') {
@@ -351,7 +355,7 @@ export class UserAndAccessSteps {
351
355
  }
352
356
 
353
357
  static toggleReadAccessForRepo(repoName) {
354
- return this.getReadAccessForRepo(repoName).realClick();
358
+ return this.getReadAccessForRepo(repoName).click(CENTER_SCROLL);
355
359
  }
356
360
 
357
361
  static validateReadAccessForRepo(repoName, expectedState) {
@@ -374,7 +378,7 @@ export class UserAndAccessSteps {
374
378
  }
375
379
 
376
380
  static toggleWriteAccessForRepo(repoName) {
377
- return this.getWriteAccessForRepo(repoName).realClick();
381
+ return this.getWriteAccessForRepo(repoName).click(CENTER_SCROLL);
378
382
  }
379
383
 
380
384
  static validateWriteAccessForRepo(repoName, expectedState) {
@@ -390,7 +394,7 @@ export class UserAndAccessSteps {
390
394
  }
391
395
 
392
396
  static clickMaintainAccessRepo(repoName) {
393
- UserAndAccessSteps.getMaintainAccessRepoCheckbox(repoName).realClick();
397
+ UserAndAccessSteps.getMaintainAccessRepoCheckbox(repoName).click(CENTER_SCROLL);
394
398
  }
395
399
 
396
400
  static toggleMaintainRepoForRepo(repoName) {
@@ -410,7 +414,7 @@ export class UserAndAccessSteps {
410
414
  }
411
415
 
412
416
  static toggleGraphqlAccessForRepo(repoName) {
413
- return this.getGraphqlAccessForRepo(repoName).realClick();
417
+ return this.getGraphqlAccessForRepo(repoName).click(CENTER_SCROLL);
414
418
  }
415
419
 
416
420
  static validateGraphqlAccessForRepo(repoName, expectedState) {
@@ -455,19 +459,19 @@ export class UserAndAccessSteps {
455
459
  .contains(repoName)
456
460
  .parent('.row')
457
461
  .find('.read')
458
- .realClick();
462
+ .click(CENTER_SCROLL);
459
463
  }
460
464
 
461
465
  static clickFreeWriteAccessRepo(repoName) {
462
466
  cy.get('.repo-fields').contains(repoName).parent('.row').as('row');
463
467
  cy.get('@row').scrollIntoView();
464
- cy.get('@row').find('.write').realClick();
468
+ cy.get('@row').find('.write').click(CENTER_SCROLL);
465
469
  }
466
470
 
467
471
  static clickFreeGraphqlAccessRepo(repoName) {
468
472
  cy.get('.repo-fields').contains(repoName).parent('.row').as('row');
469
473
  cy.get('@row').scrollIntoView();
470
- cy.get('@row').find('.graphql').realClick();
474
+ cy.get('@row').find('.graphql').click(CENTER_SCROLL);
471
475
  }
472
476
 
473
477
  }
@@ -218,4 +218,64 @@ export class YasqeSteps {
218
218
  static getActionButton(index) {
219
219
  return YasqeSteps.getActionButtons().eq(index);
220
220
  }
221
+
222
+ static getRunSplitButton() {
223
+ return YasqeSteps.getYasqe().find('query-split-button');
224
+ }
225
+
226
+ static getRunSplitButtonToggle() {
227
+ return YasqeSteps.getRunSplitButton().find('.yasqe_querySplitToggle');
228
+ }
229
+
230
+ static openRunDropdown() {
231
+ YasqeSteps.getRunSplitButtonToggle().click({scrollBehavior: false});
232
+ }
233
+
234
+ static getRunDropdownMenu() {
235
+ return YasqeSteps.getRunSplitButton().find('.ontotext-run-dropdown-menu');
236
+ }
237
+
238
+ static getRunDropdownItems() {
239
+ return YasqeSteps.getRunDropdownMenu().find('.ontotext-run-dropdown-menu-item');
240
+ }
241
+
242
+ static getRunDropdownItem(index) {
243
+ return YasqeSteps.getRunDropdownItems().eq(index);
244
+ }
245
+
246
+ static selectRunDropdownItem(index) {
247
+ YasqeSteps.openRunDropdown();
248
+ YasqeSteps.getRunDropdownItem(index).click({scrollBehavior: false});
249
+ // Hides the tooltip of the split button, which otherwise stays visible and overlaps both
250
+ // the run button and the toggle itself. The mouseleave has to be forced, because the
251
+ // tooltip covers the very element it is triggered on, which fails Cypress's actionability
252
+ // check. This mirrors YasqeSteps.clickExecuteQueryButtonAndHideTooltip.
253
+ YasqeSteps.getRunSplitButtonToggle().trigger('mouseleave', {force: true});
254
+ }
255
+
256
+ static executeExplainQueryPlan() {
257
+ YasqeSteps.selectRunDropdownItem(RunDropdownOption.EXPLAIN_PLAN);
258
+ }
259
+
260
+ static executeLlmExplainAll() {
261
+ YasqeSteps.selectRunDropdownItem(RunDropdownOption.LLM_EXPLAIN_ALL);
262
+ }
263
+
264
+ static executeLlmExplainQuery() {
265
+ YasqeSteps.selectRunDropdownItem(RunDropdownOption.LLM_EXPLAIN_QUERY);
266
+ }
267
+
268
+ static executeLlmExplainResults() {
269
+ YasqeSteps.selectRunDropdownItem(RunDropdownOption.LLM_EXPLAIN_RESULTS);
270
+ }
221
271
  }
272
+
273
+ /**
274
+ * The order of the options in the run dropdown of the query split button.
275
+ */
276
+ export const RunDropdownOption = {
277
+ 'EXPLAIN_PLAN': 0,
278
+ 'LLM_EXPLAIN_ALL': 1,
279
+ 'LLM_EXPLAIN_QUERY': 2,
280
+ 'LLM_EXPLAIN_RESULTS': 3
281
+ };
@@ -135,6 +135,21 @@ export class YasrSteps extends BaseSteps {
135
135
  return YasrSteps.getYasr().find('.dataTables_wrapper');
136
136
  }
137
137
 
138
+ static getExplainPlanPlugin() {
139
+ return YasrSteps.getYasr().find('.yasr-explain-plan-plugin');
140
+ }
141
+
142
+ /**
143
+ * While an explain plan is rendered, the regular yasr header is replaced with a plain title.
144
+ */
145
+ static getExplainPlanHeader() {
146
+ return YasrSteps.getYasr().find('.yasr-header-replacement');
147
+ }
148
+
149
+ static getExplainPlan() {
150
+ return YasrSteps.getExplainPlanPlugin().find('.explainPlanQuery');
151
+ }
152
+
138
153
  static getFullscreenButton() {
139
154
  return cy.get('.yasr-fullscreen-button');
140
155
  }
@@ -132,7 +132,7 @@ export class ClusterStubs extends Stubs {
132
132
  added.forEach((node) => {
133
133
  response[node] = "Node was successfully added in the cluster.";
134
134
  });
135
- added.forEach((node) => {
135
+ removed.forEach((node) => {
136
136
  response[node] = "Node was successfully removed from the cluster.";
137
137
  });
138
138
 
@@ -210,6 +210,13 @@ export class ClusterStubs extends Stubs {
210
210
  }).as('3-nodes-cluster-group-status-tag');
211
211
  }
212
212
 
213
+ static stubClusterGroupStatusWithoutLeader() {
214
+ cy.intercept('/rest/cluster/group/status', {
215
+ fixture: '/cluster/3-nodes-cluster-group-status-no-leader.json',
216
+ statusCode: 200
217
+ }).as('3-nodes-cluster-group-status-no-leader');
218
+ }
219
+
213
220
  static stubDeleteTag(uri) {
214
221
  cy.intercept(`rest/cluster/config/tag`, {
215
222
  body: `"Removed primary tag '${uri}' from cluster`,
@@ -225,6 +225,25 @@ export class QueryStubs {
225
225
  static interceptSavedQueryCreation() {
226
226
  cy.intercept('POST', '/rest/sparql/saved-queries').as('saveQuery');
227
227
  }
228
+
229
+ /**
230
+ * Aliases the requests towards the repository endpoint, so that an explain plan query can be
231
+ * told apart from a regular query and from the count query which yasgui sends for the
232
+ * pagination. They all go to the same endpoint and can only be distinguished by their body.
233
+ * @param {string} repositoryId - the repository against which the queries are executed.
234
+ */
235
+ static interceptQueries(repositoryId) {
236
+ cy.intercept('POST', `/repositories/${repositoryId}`, (req) => {
237
+ const body = typeof req.body === 'string' ? req.body : '';
238
+ if (body.includes('count=1')) {
239
+ req.alias = 'countQuery';
240
+ } else if (body.includes('explain=true')) {
241
+ req.alias = 'explainQuery';
242
+ } else {
243
+ req.alias = 'plainQuery';
244
+ }
245
+ });
246
+ }
228
247
  }
229
248
 
230
249
  export class QueryStubDescription {