From 21c1ea4bf92511d7034193f9afd3271ec6767370 Mon Sep 17 00:00:00 2001 From: Aref Shafaei Date: Mon, 9 Sep 2024 20:41:57 -0700 Subject: [PATCH 1/3] Migrate Recordedit add, edit, and delete specs (#2539) * migrate add, edit, multi-edit, and delete specs * fix the timestamp issue where the addOrRemove error wasnt showing up * migrate useful multi-col-types spec tests into null-values.spec.ts --- .github/workflows/e2e.yml | 9 - Makefile | 22 +- .../input-switch/date-time-field.tsx | 46 +- src/components/input-switch/file-field.tsx | 4 +- src/components/navbar/navbar.tsx | 4 +- .../parallel-configs/default-config.dev.json | 3 +- .../config/recordedit/edit-multi.dev.json | 9 - .../recordedit/multi-column-types.config.json | 15 - .../recordedit/multi-column-types.dev.json | 9 - ...ti.config.json => null-values.config.json} | 6 +- .../config/recordedit/null-values.dev.json | 9 + .../table_w_generated_columns.json | 22 - .../data/multi-form-input/main.json | 6 +- .../table_1.json | 0 .../table_2.json | 0 .../data/product/accommodation.json | 17 +- .../schema/recordedit/multi-edit.json | 282 ------ ...lti-column-types.json => null-values.json} | 302 +----- .../schema/recordedit/product-add.json | 2 +- test/e2e/locators/modal.ts | 9 + test/e2e/locators/page.ts | 4 + test/e2e/locators/recordedit.ts | 83 +- test/e2e/setup/playwright.teardown.ts | 9 - .../protractor.conf.js | 16 - .../recordedit/add.conf.js | 15 - .../recordedit/add.config.ts | 10 + .../recordedit/add.spec.js | 392 -------- .../recordedit/add.spec.ts | 340 +++++++ .../recordedit/delete.spec.js | 66 -- .../recordedit/delete.spec.ts | 47 + .../recordedit/edit-delete.conf.js | 16 - .../recordedit/edit-delete.config.ts | 11 + .../recordedit/edit.spec.js | 170 ---- .../recordedit/edit.spec.ts | 370 +++++++ .../multi-form-input-create.spec.ts | 15 +- .../multi-form-input-edit.spec.ts | 9 + .../recordedit/multi-col-types.conf.js | 15 - .../recordedit/multi-col-types.spec.js | 360 ------- .../recordedit/multi-edit.conf.js | 16 - .../recordedit/multi-edit.spec.js | 354 ------- .../recordedit/null-values.config.ts | 8 + .../recordedit/null-values.spec.ts | 130 +++ .../recordedit/remove-edit-form.spec.js | 97 -- test/e2e/utils/catalog-utils.ts | 21 +- test/e2e/utils/page-utils.ts | 44 +- test/e2e/utils/record-utils.ts | 7 +- test/e2e/utils/recordedit-utils.ts | 942 +++++++++++++++--- 47 files changed, 2013 insertions(+), 2330 deletions(-) delete mode 100644 test/e2e/data_setup/config/recordedit/edit-multi.dev.json delete mode 100644 test/e2e/data_setup/config/recordedit/multi-column-types.config.json delete mode 100644 test/e2e/data_setup/config/recordedit/multi-column-types.dev.json rename test/e2e/data_setup/config/recordedit/{edit-multi.config.json => null-values.config.json} (52%) create mode 100644 test/e2e/data_setup/config/recordedit/null-values.dev.json delete mode 100644 test/e2e/data_setup/data/multi-column-types/table_w_generated_columns.json rename test/e2e/data_setup/data/{multi-column-types => null-values}/table_1.json (100%) rename test/e2e/data_setup/data/{multi-column-types => null-values}/table_2.json (100%) delete mode 100644 test/e2e/data_setup/schema/recordedit/multi-edit.json rename test/e2e/data_setup/schema/recordedit/{multi-column-types.json => null-values.json} (61%) delete mode 100644 test/e2e/specs/all-features-confirmation/protractor.conf.js delete mode 100644 test/e2e/specs/all-features-confirmation/recordedit/add.conf.js create mode 100644 test/e2e/specs/all-features-confirmation/recordedit/add.config.ts delete mode 100644 test/e2e/specs/all-features-confirmation/recordedit/add.spec.js create mode 100644 test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts delete mode 100644 test/e2e/specs/all-features-confirmation/recordedit/delete.spec.js create mode 100644 test/e2e/specs/all-features-confirmation/recordedit/delete.spec.ts delete mode 100644 test/e2e/specs/all-features-confirmation/recordedit/edit-delete.conf.js create mode 100644 test/e2e/specs/all-features-confirmation/recordedit/edit-delete.config.ts delete mode 100644 test/e2e/specs/all-features-confirmation/recordedit/edit.spec.js create mode 100644 test/e2e/specs/all-features-confirmation/recordedit/edit.spec.ts delete mode 100644 test/e2e/specs/default-config/recordedit/multi-col-types.conf.js delete mode 100644 test/e2e/specs/default-config/recordedit/multi-col-types.spec.js delete mode 100644 test/e2e/specs/default-config/recordedit/multi-edit.conf.js delete mode 100644 test/e2e/specs/default-config/recordedit/multi-edit.spec.js create mode 100644 test/e2e/specs/default-config/recordedit/null-values.config.ts create mode 100644 test/e2e/specs/default-config/recordedit/null-values.spec.ts delete mode 100644 test/e2e/specs/default-config/recordedit/remove-edit-form.spec.js diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2fcf682cc..f79bdb48c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -167,12 +167,6 @@ jobs: echo "Waiting for Sauce Connect..." sleep 5; done; - - name: Run all features confirmation test spec (protractor) - id: test-protractor-all-features-confirmation - continue-on-error: true - run: | - cd chaise - make testallfeaturesconfirmation-protractor - name: Run default config test spec (protractor) id: test-protractor-default-config continue-on-error: true @@ -191,9 +185,6 @@ jobs: - name: Check on delete prohibited test spec if: always() && steps.test-delete-prohibited.outcome != 'success' run: exit 1 - - name: Check on all features confirmation test spec (protractor) - if: always() && steps.test-protractor-all-features-confirmation.outcome != 'success' - run: exit 1 - name: Check on default config test spec (protractor) if: always() && steps.test-protractor-default-config.outcome != 'success' run: exit 1 diff --git a/Makefile b/Makefile index 2449c821e..de8e6fc95 100644 --- a/Makefile +++ b/Makefile @@ -44,13 +44,12 @@ MODULES=node_modules ### test scripts ## Sequential test scripts # Recordedit tests -E2EDIrecordAdd=test/e2e/specs/all-features-confirmation/recordedit/add.conf.js -E2EDIrecordEditMultiColTypes=test/e2e/specs/default-config/recordedit/multi-col-types.conf.js +E2EDIrecordAdd=test/e2e/specs/all-features-confirmation/recordedit/add.config.ts +E2EDrecordEditNullValues=test/e2e/specs/default-config/recordedit/null-values.config.ts E2EDIrecordImmutable=test/e2e/specs/default-config/recordedit/immutable-inputs.conf.js -E2EDIrecordEdit=test/e2e/specs/all-features-confirmation/recordedit/edit-delete.conf.js +E2EDIrecordEdit=test/e2e/specs/all-features-confirmation/recordedit/edit-delete.config.ts # not part of the make recordedit command anymore E2EDIrecordMultiFormInput=test/e2e/specs/default-config/multi-form-input/multi-form-input.config.ts -E2EDIrecordMultiEdit=test/e2e/specs/default-config/recordedit/multi-edit.conf.js E2EDrecordEditCompositeKey=test/e2e/specs/default-config/recordedit/composite-key.config.ts E2EDrecordEditDomainFilter=test/e2e/specs/default-config/recordedit/domain-filter.config.ts E2EDrecordEditSubmissionDisabled=test/e2e/specs/default-config/recordedit/submission-disabled.conf.js @@ -80,7 +79,6 @@ E2Efooter=test/e2e/specs/all-features-confirmation/footer/playwright.config.ts # errors test E2Eerrors=test/e2e/specs/all-features-confirmation/errors/errors.config.ts ## Parallel test scripts (protractor) -AllFeaturesConfirmationParallel_PROTRACTOR=test/e2e/specs/all-features-confirmation/protractor.conf.js DefaultConfigParallel_PROTRACTOR=test/e2e/specs/default-config/protractor.conf.js ## Parallel test scripts AllFeaturesParallel=test/e2e/specs/all-features/playwright.config.ts @@ -92,19 +90,18 @@ Manualrecordset=test/manual/specs/recordset.conf.js # protractor tests RECORDSET_TESTS_PROTRACTOR=$(E2ErecordsetAdd) $(E2EDrecordsetEdit) -RECORDADD_TESTS_PROTRACTOR=$(E2EDIrecordAdd) $(E2EDIrecordMultiFormInput) $(E2EDIrecordImmutable) -RECORDEDIT_TESTS_PROTRACTOR=$(E2EDIrecordEdit) $(E2EDIrecordMultiEdit) $(E2EDrecordEditSubmissionDisabled) $(E2EDIrecordEditMultiColTypes) +RECORDADD_TESTS_PROTRACTOR=$(E2EDIrecordMultiFormInput) $(E2EDIrecordImmutable) +RECORDEDIT_TESTS_PROTRACTOR=$(E2EDrecordEditSubmissionDisabled) DEFAULT_CONFIG_PARALLEL_TESTS_PROTRACTOR=$(DefaultConfigParallel_PROTRACTOR) -ALL_FEATURES_CONFIRMATION_PARALLEL_TESTS_PROTRACTOR=$(AllFeaturesConfirmationParallel_PROTRACTOR) -PARALLEL_TESTS_PROTRACTOR=$(AllFeaturesConfirmationParallel_PROTRACTOR) $(DefaultConfigParallel_PROTRACTOR) +PARALLEL_TESTS_PROTRACTOR=$(DefaultConfigParallel_PROTRACTOR) ALL_TESTS_PROTRACTOR=$(RECORDSET_TESTS_PROTRACTOR) $(RECORDADD_TESTS_PROTRACTOR) $(RECORDEDIT_TESTS_PROTRACTOR) # playwright tests NAVBAR_TESTS=$(E2Enavbar) $(E2EnavbarHeadTitle) $(E2EnavbarCatalogConfig) RECORD_TESTS=$(E2EDrecord) $(E2ErecordNoDeleteBtn) $(E2EDrecordRelatedTable) $(E2EDrecordCopy) $(E2EDrecordLinks) RECORDSET_TESTS=$(E2EDrecordset) $(E2ErecordsetSavedQuery) $(E2EDrecordsetIndFacet) $(E2EDrecordsetHistFacet) -RECORDADD_TESTS=$(E2EDIrecordMultiFormInput) $(E2ErecordEditForeignKeyDropdown) $(E2EDrecordEditCompositeKey) -RECORDEDIT_TESTS=$(E2ErecordEditInputIframe) $(E2EDrecordEditDomainFilter) +RECORDADD_TESTS=$(E2EDIrecordAdd) $(E2EDIrecordMultiFormInput) $(E2ErecordEditForeignKeyDropdown) $(E2EDrecordEditCompositeKey) +RECORDEDIT_TESTS=$(E2EDIrecordEdit) $(E2EDrecordEditNullValues) $(E2ErecordEditInputIframe) $(E2EDrecordEditDomainFilter) PERMISSIONS_TESTS=$(E2EmultiPermissionsVisibility) FOOTER_TESTS=$(E2Efooter) ERRORS_TESTS=$(E2Eerrors) @@ -200,9 +197,6 @@ testallfeatures: test-ALL_FEATURES_PARALLEL_TESTS .PHONY: testallfeaturesconfirmation testallfeaturesconfirmation: test-ALL_FEATURES_CONFIRMATION_PARALLEL_TESTS -.PHONY: testallfeaturesconfirmation-protractor -testallfeaturesconfirmation-protractor: test_protractor-ALL_FEATURES_CONFIRMATION_PARALLEL_TESTS_PROTRACTOR - #Rule to run the delete prohibited chaise configuration tests in parallel .PHONY: testdeleteprohibited testdeleteprohibited: test-DELETE_PROHIBITED_PARALLEL_TESTS diff --git a/src/components/input-switch/date-time-field.tsx b/src/components/input-switch/date-time-field.tsx index 76dcf3d20..b7d99f46b 100644 --- a/src/components/input-switch/date-time-field.tsx +++ b/src/components/input-switch/date-time-field.tsx @@ -43,6 +43,8 @@ const DateTimeField = (props: DateTimeFieldProps): JSX.Element => { const DATE_TIME_FORMAT = props.hasTimezone ? dataFormats.datetime.return : dataFormats.timestamp; + // since we're showing a manual error, we're also setting the input as this value so the form is also invalid. + const invalidValue = 'invalid-value'; useEffect(() => { // Set default values if they exists @@ -66,17 +68,17 @@ const DateTimeField = (props: DateTimeFieldProps): JSX.Element => { const datetimeFieldState = getFieldState(props.name); // if both are missing, the input is empty - if (!dateVal && !timeVal && !props.requiredInput) { + if (!dateVal && !timeVal) { if (datetimeFieldState.error) clearErrors(props.name); setValue(props.name, ''); - trigger(props.name); + void trigger(props.name); return; } // if date is missing, this is invalid if (!dateVal) { setError(props.name, { type: CUSTOM_ERROR_TYPES.INVALID_DATE_TIME, message: ERROR_MESSAGES.INVALID_DATE }); - setValue(props.name, 'invalid-value'); + setValue(props.name, invalidValue); return; } // otherwise validate the date value @@ -84,27 +86,23 @@ const DateTimeField = (props: DateTimeFieldProps): JSX.Element => { const err = VALIDATE_VALUE_BY_TYPE['date'](dateVal); if (typeof err === 'string') { setError(props.name, { type: CUSTOM_ERROR_TYPES.INVALID_DATE_TIME, message: err }); - setValue(props.name, 'invalid-value'); + setValue(props.name, invalidValue); return; } } - // if only time is missing, use 00:00:00 for it - let timeValTemp = ''; - if (!timeVal && !props.requiredInput) { - timeValTemp = '00:00:00'; + // if time is missing, use 00:00:00 for it + let usedTimeVal = timeVal; + if (!timeVal) { + usedTimeVal = '00:00:00'; + setValue(`${props.name}-time`, usedTimeVal); } // otherwise validate the time value else { - if (!timeVal) { - setError(props.name, { type: CUSTOM_ERROR_TYPES.INVALID_DATE_TIME, message: 'Please enter a valid time' }); - setValue(props.name, 'invalid-value'); - return; - } const err = VALIDATE_VALUE_BY_TYPE['time'](timeVal); if (typeof err === 'string') { setError(props.name, { type: CUSTOM_ERROR_TYPES.INVALID_DATE_TIME, message: err }); - setValue(props.name, 'invalid-value'); + setValue(props.name, invalidValue); return; } } @@ -115,7 +113,7 @@ const DateTimeField = (props: DateTimeFieldProps): JSX.Element => { * and have to rely on moment to do this for us. */ const date = windowRef.moment(dateVal, dataFormats.date); - const time = windowRef.moment(timeValTemp ? timeValTemp : timeVal, dataFormats.time); + const time = windowRef.moment(usedTimeVal, dataFormats.time); const dateTime = date.set({ hour: time.get('hour'), minute: time.get('minute'), @@ -129,9 +127,23 @@ const DateTimeField = (props: DateTimeFieldProps): JSX.Element => { // we have to call trigger to trigger all the validators again // (needed for the ARRAY_ADD_OR_DISCARD_VALUE error to show up) - trigger(props.name); + void trigger(props.name); - }, [dateVal, timeVal]) + }, [dateVal, timeVal]); + + /** + * we have to make sure to trigger all the validators after we've manually changed the value. + * + * NOTE: + * - This is needed for the ARRAY_ADD_OR_DISCARD_VALUE error or any other custom validators that we have to show up. + */ + useEffect(() => { + /** + * if we set the value to "invalid value", we also have defined a custom error that we want to show. + * so we're excluding that from here to make sure the custom error is not replaced by a generic one. + */ + if (dateTimeVal !== invalidValue) void trigger(props.name); + }, [dateTimeVal]) const formInputDate = useController({ name: `${props.name}-date`, diff --git a/src/components/input-switch/file-field.tsx b/src/components/input-switch/file-field.tsx index 5aecb3b41..381e54b25 100644 --- a/src/components/input-switch/file-field.tsx +++ b/src/components/input-switch/file-field.tsx @@ -160,14 +160,14 @@ const FileField = (props: FileFieldProps): JSX.Element => { >
{renderInput(field.value, showClear, clearInput)} - {!props.disableInput && + {!props.disableInput &&
-
} + }
{renderImagePreview(field.value)} diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx index d8b2a96d5..a1288eee2 100644 --- a/src/components/navbar/navbar.tsx +++ b/src/components/navbar/navbar.tsx @@ -67,7 +67,7 @@ const ChaiseNavbar = (): JSX.Element => { const isVersioned = (): boolean => (!!catalogId.split('@')[1]); useEffect(() => { - const root = { ...cc.navbarMenu } || {}; + const root = typeof cc.navbarMenu === 'object' ? { ...cc.navbarMenu } : {}; // if in iframe and we want to force links to open in new tab, const forceNewTab = settings.openLinksInTab === true; @@ -195,7 +195,7 @@ const ChaiseNavbar = (): JSX.Element => { /** * Update the state to reflect most recently opened dropdown */ - setOpenedDropDownIndex(isOpen ? index : undefined); + setOpenedDropDownIndex(isOpen ? index : undefined); onDropdownToggle(isOpen, event, LogActions.NAVBAR_MENU_OPEN, item); } diff --git a/test/e2e/data_setup/config/parallel-configs/default-config.dev.json b/test/e2e/data_setup/config/parallel-configs/default-config.dev.json index 204dc36da..c8b63f2db 100644 --- a/test/e2e/data_setup/config/parallel-configs/default-config.dev.json +++ b/test/e2e/data_setup/config/parallel-configs/default-config.dev.json @@ -8,8 +8,7 @@ "recordedit/composite-key.config.json", "recordedit/domain-filter.config.json", "recordedit/foreign-key-dropdown.config.json", - "recordedit/multi-column-types.config.json", - "recordedit/edit-multi.config.json", + "recordedit/null-values.config.json", "recordedit/submission-disabled.config.json", "recordset/add.config.json", diff --git a/test/e2e/data_setup/config/recordedit/edit-multi.dev.json b/test/e2e/data_setup/config/recordedit/edit-multi.dev.json deleted file mode 100644 index 2e6379ba4..000000000 --- a/test/e2e/data_setup/config/recordedit/edit-multi.dev.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "setup": { - "schemaConfigurations": [ - "recordedit/edit-multi.config.json" - ], - "schema": "multi-edit" - }, - "cleanup" : true -} diff --git a/test/e2e/data_setup/config/recordedit/multi-column-types.config.json b/test/e2e/data_setup/config/recordedit/multi-column-types.config.json deleted file mode 100644 index 9ca96ceca..000000000 --- a/test/e2e/data_setup/config/recordedit/multi-column-types.config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "catalog": {}, - "schema": { - "name": "multi-column-types", - "createNew": true, - "path": "test/e2e/data_setup/schema/recordedit/multi-column-types.json" - }, - "tables": { - "createNew": true - }, - "entities": { - "createNew": true, - "path": "test/e2e/data_setup/data/multi-column-types" - } -} diff --git a/test/e2e/data_setup/config/recordedit/multi-column-types.dev.json b/test/e2e/data_setup/config/recordedit/multi-column-types.dev.json deleted file mode 100644 index 79ab8b9ea..000000000 --- a/test/e2e/data_setup/config/recordedit/multi-column-types.dev.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "setup": { - "schemaConfigurations": [ - "/recordedit/multi-column-types.config.json" - ], - "schema": "multi-column-types" - }, - "cleanup": true -} diff --git a/test/e2e/data_setup/config/recordedit/edit-multi.config.json b/test/e2e/data_setup/config/recordedit/null-values.config.json similarity index 52% rename from test/e2e/data_setup/config/recordedit/edit-multi.config.json rename to test/e2e/data_setup/config/recordedit/null-values.config.json index 4173f8266..c7938bffd 100644 --- a/test/e2e/data_setup/config/recordedit/edit-multi.config.json +++ b/test/e2e/data_setup/config/recordedit/null-values.config.json @@ -1,15 +1,15 @@ { "catalog": {}, "schema": { - "name": "multi-edit", + "name": "null-values", "createNew": true, - "path": "test/e2e/data_setup/schema/recordedit/multi-edit.json" + "path": "test/e2e/data_setup/schema/recordedit/null-values.json" }, "tables": { "createNew": true }, "entities": { "createNew": true, - "path": "test/e2e/data_setup/data/multi-edit" + "path": "test/e2e/data_setup/data/null-values" } } diff --git a/test/e2e/data_setup/config/recordedit/null-values.dev.json b/test/e2e/data_setup/config/recordedit/null-values.dev.json new file mode 100644 index 000000000..925e8c236 --- /dev/null +++ b/test/e2e/data_setup/config/recordedit/null-values.dev.json @@ -0,0 +1,9 @@ +{ + "setup": { + "schemaConfigurations": [ + "/recordedit/null-values.config.json" + ], + "schema": "null-values" + }, + "cleanup": true +} diff --git a/test/e2e/data_setup/data/multi-column-types/table_w_generated_columns.json b/test/e2e/data_setup/data/multi-column-types/table_w_generated_columns.json deleted file mode 100644 index 772463127..000000000 --- a/test/e2e/data_setup/data/multi-column-types/table_w_generated_columns.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "id": 1, - "int2_col_gen": 32767, - "int4_col_gen": -2147483648, - "int8_col_gen": 9007199254740991, - "float4_col_gen": 4.6123, - "float8_col_gen": 234523523.023045, - "text_col_gen": "sample", - "longtext_col_gen": "asjdf;laksjdf;laj ;lkajsd;f lkajsdf;lakjs f;lakjs df;lasjd f;ladsjf;alskdjfa ;lskdjf a;lsdkjf a;lskdfjal;sdkfj as;ldfkj as;dlf kjasl;fkaj;lfkjasl;fjas;ldfkjals;dfkjas;dlkfja;sldkfjasl;dkfjas;dlfkjasl;dfkja; lsdjfk a;lskdjf a;lsdfj as;ldfja;sldkfja;lskjdfa;lskdjfa;lsdkfja;sldkfjas;ldfkjas;dlfkjas;lfkja;sldkjf a;lsjf ;laskj fa;slk jfa;sld fjas;l js;lfkajs;lfkasjf;alsja;lk ;l kja", - "markdown_col_gen": "Sample", - "bool_true_col_gen": true, - "bool_false_col_gen": false, - "timestamp_col_gen": "2016-01-18T13:00:00", - "timestamptz_col_gen": "2016-01-18T00:00:00-08:00", - "date_col_gen": "2016-08-15", - "json_col_gen": "82.89", - "fk_col_gen": 1, - "asset_col_gen": "http://test.com/hatract/test", - "color_rg_hex_gen": "#123456" - } -] diff --git a/test/e2e/data_setup/data/multi-form-input/main.json b/test/e2e/data_setup/data/multi-form-input/main.json index 48934fd2b..cd90df436 100644 --- a/test/e2e/data_setup/data/multi-form-input/main.json +++ b/test/e2e/data_setup/data/multi-form-input/main.json @@ -3,12 +3,14 @@ "id": 9001, "markdown_col": "markdown value 9001", "text_col": "text value 9001", - "int_col": 9001 + "int_col": 9001, + "fk_col": "1" }, { "id": 9002, "markdown_col": "markdown value 9002", "int_col": 9002, - "date_col": "2023-11-11" + "date_col": "2023-11-11", + "fk_col": "2" } ] diff --git a/test/e2e/data_setup/data/multi-column-types/table_1.json b/test/e2e/data_setup/data/null-values/table_1.json similarity index 100% rename from test/e2e/data_setup/data/multi-column-types/table_1.json rename to test/e2e/data_setup/data/null-values/table_1.json diff --git a/test/e2e/data_setup/data/multi-column-types/table_2.json b/test/e2e/data_setup/data/null-values/table_2.json similarity index 100% rename from test/e2e/data_setup/data/multi-column-types/table_2.json rename to test/e2e/data_setup/data/null-values/table_2.json diff --git a/test/e2e/data_setup/data/product/accommodation.json b/test/e2e/data_setup/data/product/accommodation.json index 0840739fc..62cf394dd 100644 --- a/test/e2e/data_setup/data/product/accommodation.json +++ b/test/e2e/data_setup/data/product/accommodation.json @@ -34,6 +34,13 @@ "json_col_with_markdown": "delivered", "date_col": "12/9/2008", "luxurious": true, + "text_array": ["val1"], + "boolean_array": [false, false], + "int4_array": [1, 2, 3, 4, 5], + "float4_array": [-5.2], + "date_array": null, + "timestamp_array": null, + "timestamptz_array": null, "color_rgb_hex_column": "#223456" }, { @@ -75,6 +82,13 @@ "luxurious": false, "nullable_assoc_key2": "value", "fk_col_outbound1": "o1_2", + "text_array": ["val2"], + "boolean_array": [false, false, true], + "int4_array": [1, 2, 3], + "float4_array": [1.1, -5], + "date_array": null, + "timestamp_array": null, + "timestamptz_array": ["2022-02-02T02:02:02-08:00", "2024-04-04T04:04:04-08:00"], "color_rgb_hex_column": "#423456" }, { @@ -117,4 +131,5 @@ "timestamp_array": ["2003-03-03T03:03:03"], "timestamptz_array": ["2002-02-02T02:02:02-08:00"], "color_rgb_hex_column": "#623456" -}] +} +] diff --git a/test/e2e/data_setup/schema/recordedit/multi-edit.json b/test/e2e/data_setup/schema/recordedit/multi-edit.json deleted file mode 100644 index 8f8da64d8..000000000 --- a/test/e2e/data_setup/schema/recordedit/multi-edit.json +++ /dev/null @@ -1,282 +0,0 @@ -{ - "schema_name": "multi-edit", - "tables": { - "multi-edit-table": { - "comment": "Table to represent adding multiple entities", - "kind": "table", - "keys": [ - { - "comment": null, - "annotations": {}, - "unique_columns": [ - "id" - ] - } - ], - "foreign_keys": [ - { - "names": [["multi-edit", "multi_edit_fk1"]], - "foreign_key_columns": [ - { - "column_name": "fk_to_f1", - "table_name": "multi-edit-table", - "schema_name": "multi-edit" - } - ], - "referenced_columns": [ - { - "column_name": "id", - "table_name": "f1", - "schema_name": "multi-edit" - } - ] - } - ], - "table_name": "multi-edit-table", - "schema_name": "multi-edit", - "column_definitions": [ - { - "name": "id", - "default": null, - "nullok": false, - "type": { - "typename": "serial4" - }, - "annotations": { - "comment": [ - "hidden" - ], - "tag:isrd.isi.edu,2016:generated": null, - "tag:isrd.isi.edu,2016:immutable": null - } - }, { - "name": "text", - "nullok": true, - "type": { - "typename": "text" - } - }, { - "name": "int", - "nullok": true, - "type": { - "typename": "int" - } - }, { - "name": "json_col", - "default": null, - "nullok": true, - "type": { - "typename": "json" - }, - "annotations": {} - }, { - "name": "jsonb_col", - "default": null, - "nullok": true, - "type": { - "typename": "jsonb" - }, - "annotations": {} - }, { - "name": "fk_to_f1", - "nullok": true, - "type": { - "typename": "int4" - } - } - ], - "annotations": { - "tag:isrd.isi.edu,2016:visible-columns" : { - "*": ["id", "text", "int", "json_col", "jsonb_col", ["multi-edit", "multi_edit_fk1"]] - } - } - }, - "table_w_multiple_assets": { - "comment": "table that has three file assets", - "kind": "table", - "keys": [ - { - "comment": null, - "annotations": {}, - "unique_columns": [ - "id" - ] - } - ], - "foreign_keys": [], - "table_name": "table_w_multiple_assets", - "schema_name": "multi-edit", - "column_definitions": [ - { - "name": "id", - "nullok": false, - "type": { - "typename": "serial4" - } - }, { - "name": "col_visible", - "type": { - "typename": "text" - } - }, { - "name": "generated_col_visible", - "type": { - "typename": "text" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, { - "name": "generated_col_invisible", - "type": { - "typename": "text" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, { - "name": "file_1", - "comment": "url_pattern combination of visible column and md5", - "type": { - "typename": "text" - }, - "annotations": { - "tag:isrd.isi.edu,2017:asset": { - "url_pattern":"/hatrac/js/chaise/{{{_timestamp_txt}}}/{{{_col_visible}}}/{{{_file_1.md5_hex}}}", - "filename_column" : "file_1_filename", - "byte_count_column": "file_1_bytes", - "md5": "file_1_md5" - } - } - }, { - "name": "file_1_filename", - "type": { - "typename": "text" - } - }, { - "name": "file_1_bytes", - "type": { - "typename": "int" - } - }, { - "name": "file_1_md5", - "type": { - "typename": "text" - } - }, { - "name": "file_2", - "comment": "url_pattern combination of visible generated column and md5", - "type": { - "typename": "text" - }, - "annotations": { - "tag:isrd.isi.edu,2017:asset": { - "url_pattern":"/hatrac/js/chaise/{{{_timestamp_txt}}}/{{{_generated_col_visible}}}/{{{_file_2.md5_hex}}}", - "filename_column" : "file_2_filename", - "byte_count_column": "file_2_bytes", - "md5": "file_2_md5" - } - } - }, { - "name": "file_2_filename", - "type": { - "typename": "text" - } - }, { - "name": "file_2_bytes", - "type": { - "typename": "int" - } - }, { - "name": "file_2_md5", - "type": { - "typename": "text" - } - }, { - "name": "file_3", - "comment": "url_pattern combination of invisible generated column and md5", - "type": { - "typename": "text" - }, - "annotations": { - "tag:isrd.isi.edu,2017:asset": { - "url_pattern":"/hatrac/js/chaise/{{{_timestamp_txt}}}/{{{_generated_col_invisible}}}/{{{_file_3.md5_hex}}}", - "filename_column" : "file_3_filename", - "byte_count_column": "file_3_bytes", - "md5": "file_3_md5" - } - } - }, { - "name": "file_3_filename", - "type": { - "typename": "text" - } - }, { - "name": "file_3_bytes", - "type": { - "typename": "int" - } - }, { - "name": "file_3_md5", - "type": { - "typename": "text" - } - }, { - "comment": null, - "name": "timestamp_txt", - "type": { - "typename": "text" - } - } - ], - "annotations": { - "tag:isrd.isi.edu,2016:visible-columns" : { - "*": [ - "file_1", "file_1_filename", "file_1_bytes", - "file_2", "file_2_filename", "file_2_bytes", - "file_3", "file_3_filename", "file_3_bytes" - ], - "entry": [ - "col_visible", "generated_col_visible", "file_1", "file_2", "file_3", "timestamp_txt" - ] - } - } - }, - "f1": { - "kind": "table", - "table_name": "f1", - "schema_name": "multi-edit", - "comment": "main table has fk to this table", - "keys": [ - {"unique_columns": ["id"]}, - {"unique_columns": ["term"]} - ], - "foreign_keys": [ - ], - "column_definitions": [ - { - "name": "id", - "nullok": false, - "type": { - "typename": "serial4" - } - }, - { - "name": "term", - "nullok": false, - "type": { - "typename": "text" - } - } - ], - "annotations": {} - } - }, - "table_names": [ - "multi-edit-table", - "table_w_multiple_assets", - "f1" - ], - "comment": null, - "annotations": {} -} diff --git a/test/e2e/data_setup/schema/recordedit/multi-column-types.json b/test/e2e/data_setup/schema/recordedit/null-values.json similarity index 61% rename from test/e2e/data_setup/schema/recordedit/multi-column-types.json rename to test/e2e/data_setup/schema/recordedit/null-values.json index e22c2a165..385e1c790 100644 --- a/test/e2e/data_setup/schema/recordedit/multi-column-types.json +++ b/test/e2e/data_setup/schema/recordedit/null-values.json @@ -1,11 +1,11 @@ { - "schema_name": "multi-column-types", - "comment": "A schema composed of 3 tables: a main table (table_1) that has nearly all types of columns for testing in RecordEdit app, a 2nd table (table_w_generated_columns) that has generated versions of different column types, and another table (table_2) that supplies a foreign key to table_1.", + "schema_name": "null-values", + "comment": "", "tables": { "table_1": { "table_name": "table_1", "comment": "The main table with different columns that map to a different input controls in RecordEdit", - "schema_name": "multi-column-types", + "schema_name": "null-values", "kind": "table", "column_definitions": [ { @@ -362,7 +362,7 @@ "type": {"typename": "int"} }, { - "comment": "A color_rgb_hex column with an initial value of null", + "comment": "A color_rgb_hex column with an initial value", "name": "color_rgb_hex_col", "default": null, "nullok": true, @@ -378,7 +378,33 @@ "type": { "typename": "color_rgb_hex" } - } + }, + { + "comment": "Has initial value", + "name": "array_text_col", + "default": null, + "nullok": true, + "type": { + "is_array": true, + "typename": "text[]", + "base_type": { + "typename": "text" + } + } + }, + { + "comment": "Doesn't have any initial values.", + "name": "array_text_null_col", + "default": null, + "nullok": true, + "type": { + "is_array": true, + "typename": "text[]", + "base_type": { + "typename": "text" + } + } + } ], "keys": [ { @@ -392,18 +418,18 @@ "foreign_keys": [ { "comment": "Describes the fk relationship between table_1:fk_null_col and table_2:id", - "names": [["multi-column-types", "table_1_fk_1"]], + "names": [["null-values", "table_1_fk_1"]], "foreign_key_columns": [ { "table_name": "table_1", - "schema_name": "multi-column-types", + "schema_name": "null-values", "column_name": "fk_null_col" } ], "referenced_columns": [ { "table_name": "table_2", - "schema_name": "multi-column-types", + "schema_name": "null-values", "column_name": "id" } ], @@ -411,18 +437,18 @@ }, { "comment": "Describes the fk relationship between table_1:fk_col and table_2:id", - "names": [["multi-column-types", "table_1_fk_2"]], + "names": [["null-values", "table_1_fk_2"]], "foreign_key_columns": [ { "table_name": "table_1", - "schema_name": "multi-column-types", + "schema_name": "null-values", "column_name": "fk_col" } ], "referenced_columns": [ { "table_name": "table_2", - "schema_name": "multi-column-types", + "schema_name": "null-values", "column_name": "id" } ], @@ -430,262 +456,10 @@ } ] }, - "table_w_generated_columns": { - "table_name": "table_w_generated_columns", - "comment": "This is a table of generated columns. The reason these columns are separate from the larger table_1 table is because table_1 was getting too large. ermrestjs would construct /attributegroup/ PUT request urls that uses all of a table's visible columns. When that exceeds 32, postgres complains that the composite key is too large.", - "schema_name": "multi-column-types", - "kind": "table", - "column_definitions": [ - { - "comment": null, - "name": "id", - "default": null, - "nullok": false, - "type": { - "typename": "int2" - }, - "annotations": {} - }, - { - "comment": "An generated int2 column with an initial non-null value", - "name": "int2_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "int2" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated int4 column with an initial non-null value", - "name": "int4_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "int4" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated int8 column with an initial non-null value", - "name": "int8_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "int8" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated float4 column with an initial non-null value", - "name": "float4_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "float4" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated float8 column with an initial non-null value", - "name": "float8_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "float8" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated text column with an initial non-null value", - "name": "text_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "text" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated longtext column with an initial non-null value", - "name": "longtext_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "longtext" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated markdown column with an initial non-null value", - "name": "markdown_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "markdown" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated boolean column with an initial true value", - "name": "bool_true_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "boolean" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated boolean column with an initial false value", - "name": "bool_false_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "boolean" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated timestamp column with an initial non-null value", - "name": "timestamp_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "timestamp" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated timestamptz column with an initial non-null value", - "name": "timestamptz_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "timestamptz" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated date column with an initial non-null value", - "name": "date_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "date" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated json column with an initial non-null value", - "name": "json_col_gen", - "default": 82.89, - "nullok": true, - "type": { - "typename": "json" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": 82.89 - } - }, - { - "comment": "A generated foreign key column with an initial non-null value", - "name": "fk_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "int2" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - }, - { - "comment": "A generated asset column with an initial non-null value", - "name": "asset_col_gen", - "default": null, - "nullok": true, - "type": { - "typename": "text" - }, - "annotations": { - "tag:isrd.isi.edu,2017:asset": null - } - }, - { - "comment": "A generated color_rgb_hex column with an initial non-null value", - "name": "color_rgb_hex_gen", - "default": null, - "nullok": true, - "type": { - "typename": "color_rgb_hex" - }, - "annotations": { - "tag:isrd.isi.edu,2016:generated": null - } - } - ], - "keys": [ - { - "comment": null, - "annotations": {}, - "unique_columns": [ - "id" - ] - } - ], - "foreign_keys": [ - { - "comment": "Describes the fk relationship between table_w_generated_columns:fk_col_gen and table_2:id", - "foreign_key_columns": [ - { - "table_name": "table_w_generated_columns", - "schema_name": "multi-column-types", - "column_name": "fk_col_gen" - } - ], - "referenced_columns": [ - { - "table_name": "table_2", - "schema_name": "multi-column-types", - "column_name": "id" - } - ], - "annotations": {} - } - ], - "annotations": {} - }, "table_2": { "table_name": "table_2", "comment": "The secondary table that supplies the columns for a fk relationship to the main table (table_1).", - "schema_name": "multi-column-types", + "schema_name": "null-values", "kind": "table", "column_definitions": [ { diff --git a/test/e2e/data_setup/schema/recordedit/product-add.json b/test/e2e/data_setup/schema/recordedit/product-add.json index 688faa91e..2fd83ad0d 100644 --- a/test/e2e/data_setup/schema/recordedit/product-add.json +++ b/test/e2e/data_setup/schema/recordedit/product-add.json @@ -216,7 +216,7 @@ "default": null, "nullok": true, "type": { - "typename": "int4" + "typename": "int2" }, "annotations": { "comment" : ["top"], diff --git a/test/e2e/locators/modal.ts b/test/e2e/locators/modal.ts index 3bcd1bac6..356a98d36 100644 --- a/test/e2e/locators/modal.ts +++ b/test/e2e/locators/modal.ts @@ -64,6 +64,10 @@ export default class ModalLocators { return page.locator('.confirm-iframe-close-modal'); } + static getMarkdownPreviewModal(page: Page): Locator { + return page.locator('.chaise-preview-markdown'); + } + // ------------- common modal functions -------------- // static getModalTitle(modal: Locator) { @@ -158,4 +162,9 @@ export default class ModalLocators { static getIframeFieldModalSpinner(modal: Locator): Locator { return modal.locator('.iframe-field-modal-spinner') } + + // --------- markdown-preview modal functions ------------ // + static getMarkdownPreviewContent(modal: Locator): Locator { + return ModalLocators.getModalText(modal).locator('.markdown-container'); + } } diff --git a/test/e2e/locators/page.ts b/test/e2e/locators/page.ts index 25a591864..691fa367c 100644 --- a/test/e2e/locators/page.ts +++ b/test/e2e/locators/page.ts @@ -17,4 +17,8 @@ export default class PageLocators { static getFooterLink(page: Page): Locator { return PageLocators.getFooterContainer(page).locator('a'); } + + static getHelpPageMainTable(page: Page): Locator { + return page.locator('#mainTable') + } } diff --git a/test/e2e/locators/recordedit.ts b/test/e2e/locators/recordedit.ts index fabb869c1..c6f4e7055 100644 --- a/test/e2e/locators/recordedit.ts +++ b/test/e2e/locators/recordedit.ts @@ -99,6 +99,18 @@ export default class RecordeditLocators { return container.locator('.entity-key-column > .entity-key > span.column-displayname > span'); } + static getColumnNamesWithTooltip(container: Locator | Page): Locator { + return container.locator('.entity-key-column > .entity-key > span.column-displayname.chaise-icon-for-tooltip > span'); + } + + static getColumnNameByColumnIndex(container: Locator | Page, index: number): Locator { + return container.locator(`.entity-key-column > .entity-key.entity-key-${index} > span.column-displayname > span`); + } + + static getColumnInlineComments(container: Locator | Page): Locator { + return container.locator('.inline-comment-row'); + } + static getColumnRequiredIcon(colNameElement: Locator): Locator { return colNameElement.locator('xpath=./../..').locator('.text-danger'); } @@ -150,7 +162,7 @@ export default class RecordeditLocators { } static getTimestampInputsForAColumn(container: Locator | Page, name: string, formNumber: number): { - date: Locator, time: Locator, nowBtn: Locator, clearBtn: Locator + date: Locator, time: Locator, nowBtn: Locator, clearBtn: Locator, dateRemoveBtn: Locator, timeRemoveBtn: Locator } { formNumber = formNumber || 1; const inputName = `c_${formNumber}-${name}`; @@ -159,7 +171,9 @@ export default class RecordeditLocators { date: wrapper.locator(`.${inputName}-date`), time: wrapper.locator(`.${inputName}-time`), nowBtn: wrapper.locator('.date-time-now-btn'), - clearBtn: wrapper.locator('.date-time-clear-btn') + clearBtn: wrapper.locator('.date-time-clear-btn'), + dateRemoveBtn: wrapper.locator('.input-switch-date .input-switch-clear'), + timeRemoveBtn: wrapper.locator('.input-switch-time .input-switch-clear') }; } @@ -169,10 +183,25 @@ export default class RecordeditLocators { return container.locator(`.input-switch-container-${inputName}`).locator('.chaise-input-control'); } - static getErrorMessageForAColumn(container: Locator | Page, name: string, formNumber: number): Locator { + static getInputRemoveButton(container: Locator | Page, name: string, formNumber: number): Locator { + formNumber = formNumber || 1; + const inputName = `c_${formNumber}-${name}`; + return container.locator(`.input-switch-container-${inputName}`).locator('.remove-input-btn'); + } + + /** + * return the error message that is displayed for an input + * @param container the container that the input is part of + * @param name name of the input + * @param formNumber the form number value + * @param isWarning whether this is for the warning messages or not (.e.g. the add or remove warning of arrays) + * @returns + */ + static getErrorMessageForAColumn(container: Locator | Page, name: string, formNumber: number, isWarning?: boolean): Locator { formNumber = formNumber || 1; const inputName = `c_${formNumber}-${name}`; - return container.locator(`.input-switch-container-${inputName}`).locator('.input-switch-error'); + const erroClass = isWarning ? 'input-switch-error-warning' : 'input-switch-error-danger'; + return container.locator(`.input-switch-container-${inputName}`).locator('.input-switch-error.' + erroClass); } static getColumnPermissionOverlay(container: Locator | Page, columnDisplayName: string, formNumber: number): Locator { @@ -209,14 +238,14 @@ export default class RecordeditLocators { static async getColorInputBackground(page: Page, name: string, formNumber: number): Promise { formNumber = formNumber || 1; - return await page.evaluate(async () => { - const inputName = `c_${formNumber}-${name}`; + const inputName = `c_${formNumber}-${name}`; + return await page.evaluate(async ({ inputName }) => { const el = document.querySelector(`.input-switch-container-${inputName} .chaise-color-picker-preview`) as HTMLElement; const ctx = document.createElement('canvas').getContext('2d'); if (!ctx || !el) return ''; ctx.fillStyle = el.style.backgroundColor; return ctx.fillStyle; - }) + }, { inputName }); } static getColorInputBtn(container: Locator | Page, name: string, formNumber: number): Locator { @@ -358,6 +387,7 @@ export default class RecordeditLocators { } // ------------- array selectors ----------------- // + static getArrayFieldContainer(container: Locator | Page, name: string, formNumber: number) { formNumber = formNumber || 1; const inputName = `c_${formNumber}-${name}`; @@ -365,19 +395,50 @@ export default class RecordeditLocators { } /** - * TODO this only supports array of texts for now and should be changed later for other types. + * Returns the elemens that are needed for testing arrays. + * + * if you would like to test each individual input, use the `getArrayInputName` function to find its appropriate + * name and then use the other functions that are used for inptus (e.g. `getInputForAColumn`). */ - static getArrayFieldElements(container: Locator | Page, name: string, formNumber: number, baseType: string) { + static getArrayFieldElements(container: Locator | Page, name: string, formNumber: number) { formNumber = formNumber || 1; const inputName = `c_${formNumber}-${name}`; const elem = container.locator(`.array-input-field-container-${inputName}`); return { container: elem, addItemContainer: elem.locator('.add-element-container'), - addItemInput: elem.locator('.add-element-container input'), addItemButton: elem.locator('.add-button'), removeItemButtons: elem.locator('.array-remove-button'), - inputs: elem.locator('li input') }; } + + /** + * Returns the input name that chaise uses for the n-th input for an array column. + * + * @param name the name of the column + * @param index the index of the value (use -1 if it's for the "new-item" one) + * @returns + */ + static getArrayInputName(name: string, index: number) { + const append = index === -1 ? 'new-item' : `${index}-val`; + return `${name}-${append}`; + } + + + // ------------- markdown selectors ----------------- // + static getMarkdownElements(container: Locator | Page, name: string, formNumber: number) { + formNumber = formNumber || 1; + const inputName = `c_${formNumber}-${name}`; + const elem = container.locator(`.input-switch-container-${inputName}`); + + return { + container: elem, + getButton: (title: string) => elem.locator(`button[title="${title}"]`), + helpButton: elem.locator('button[title="Help"]'), + previewButton: elem.locator('button[title="Preview"]'), + fullScreenButton: elem.locator('button[title="Fullscreen Preview"]'), + previewContent: elem.locator('.md-preview .markdown-container') + } + + } } diff --git a/test/e2e/setup/playwright.teardown.ts b/test/e2e/setup/playwright.teardown.ts index de865496c..5605c7a13 100644 --- a/test/e2e/setup/playwright.teardown.ts +++ b/test/e2e/setup/playwright.teardown.ts @@ -4,7 +4,6 @@ import fs from 'fs'; import { TestOptions } from '@isrd-isi-edu/chaise/test/e2e/setup/playwright.model'; import { removeAllCatalogs } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; import { ENTITIES_PATH } from '@isrd-isi-edu/chaise/test/e2e/utils/constants'; -import axios from 'axios'; async function globalTeardown(config: FullConfig) { /** @@ -27,14 +26,6 @@ async function globalTeardown(config: FullConfig) { } } - // TODO - // if (testConfiguration.hatracNamespaces && testConfiguration.hatracNamespaces.length > 0) { - // // cleanup the hatrac namespaces - // for (const ns of testConfiguration.hatracNamespaces) { - // const response = await axios(ns, { method: 'DELETE', headers: { Cookie: process.env.AUTH_COOKIE! } }); - // } - // } - // remove the created catalogs if (testConfiguration.cleanup && testConfiguration.setup) { await removeAllCatalogs(); diff --git a/test/e2e/specs/all-features-confirmation/protractor.conf.js b/test/e2e/specs/all-features-confirmation/protractor.conf.js deleted file mode 100644 index d04b8451c..000000000 --- a/test/e2e/specs/all-features-confirmation/protractor.conf.js +++ /dev/null @@ -1,16 +0,0 @@ -var pConfig = require('./../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - // This config is meant to be run as part of the parallel tests configuration - configFileName: 'parallel-configs/all-features-confirmation.dev.json', - specs: [ - "*/*.spec.js" - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - }, - chaiseConfigFilePath: 'test/e2e/specs/all-features-confirmation/chaise-config.js' -}); - -exports.config = config; diff --git a/test/e2e/specs/all-features-confirmation/recordedit/add.conf.js b/test/e2e/specs/all-features-confirmation/recordedit/add.conf.js deleted file mode 100644 index 30bf968f6..000000000 --- a/test/e2e/specs/all-features-confirmation/recordedit/add.conf.js +++ /dev/null @@ -1,15 +0,0 @@ -var pConfig = require('./../../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - configFileName: 'recordedit/add.dev.json', - specs: [ - "add.spec.js" - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - }, - chaiseConfigFilePath: 'test/e2e/specs/all-features-confirmation/chaise-config.js' -}); - -exports.config = config; diff --git a/test/e2e/specs/all-features-confirmation/recordedit/add.config.ts b/test/e2e/specs/all-features-confirmation/recordedit/add.config.ts new file mode 100644 index 000000000..95e8974a6 --- /dev/null +++ b/test/e2e/specs/all-features-confirmation/recordedit/add.config.ts @@ -0,0 +1,10 @@ +import getConfig from '@isrd-isi-edu/chaise/test/e2e/setup/playwright.configuration'; + +export default getConfig({ + testName: 'all-features-confirmation/recordedit/add', + configFileName: 'recordedit/add.dev.json', + mainSpecName: 'all-features-confirmation', + testMatch: [ + 'add.spec.ts' + ] +}); diff --git a/test/e2e/specs/all-features-confirmation/recordedit/add.spec.js b/test/e2e/specs/all-features-confirmation/recordedit/add.spec.js deleted file mode 100644 index cf59129b1..000000000 --- a/test/e2e/specs/all-features-confirmation/recordedit/add.spec.js +++ /dev/null @@ -1,392 +0,0 @@ -var testConfiguration = browser.params.configuration; -var chaisePage = require('../../../utils/chaise.page.js'); -var recordEditHelpers = require('../../../utils/recordedit-helpers.js'), chance = require('chance').Chance(); -var moment = require('moment'); - -// take a look at the comments in recordedit-helpers.js for the expected structure of tableParams. -var currentTimestampTime = moment().format("x"); -var testParams = { - tables: [{ - comment: "general case", - schema_name: "product-add", - table_name: "accommodation", - table_displayname: "Accommodations", - table_comment: "List of different types of accommodations", - test_results: true, - primary_keys: ["id"], - columns: [ - { name: "id", generated: true, immutable: true, title: "Id", type: "serial4", nullok: false}, - { name: "title", title: "Name of Accommodation", type: "text", nullok: false}, - { name: "website", title: "Website", type: "text", comment: "A valid url of the accommodation"}, - { name: "category", title: "Category", type: "text", nullok: false, isForeignKey: true, count: 5, totalCount: 5, comment: "Type of accommodation (Resort, Hotel, or Motel)"}, // the total count is the total number of rows in the category.json data file - { name: "rating", title: "User Rating", type: "float4", nullok: false}, - { name: "summary", title: "Summary", type: "longtext", nullok: false, inline_comment: "A comment displayed for the summary column"}, - { name: "description", title: "Description", type: "markdown"}, - { name: "json_col", title: "json_col", type:"json"}, - { name: "no_of_rooms", title: "Number of Rooms", type: "int2", inline_comment: 'This shows the number of rooms!'}, - { name: "opened_on", title: "Operational Since", type: "timestamptz", nullok: false }, - { name: "date_col", title: "date_col", type: "date"}, - { name: "luxurious", title: "Is Luxurious", type: "boolean", nullok: false }, - { name: "text_array", title: "text_array", type: "array", baseType: "text" }, - { name: "boolean_array", title: "boolean_array", type: "array", baseType: "boolean" }, - { name: "int4_array", title: "int4_array", type: "array", baseType: "integer" }, - { name: "float4_array", title: "float4_array", type: "array", baseType: "number" }, - { name: "date_array", title: "date_array", type: "array", baseType: "date" }, - { name: "timestamp_array", title: "timestamp_array", type: "array", baseType: "timestamp" }, - { name: "timestamptz_array", title: "timestamptz_array", type: "array", baseType: "timestamptz" }, - { name: "color_rgb_hex_column", title: "color_rgb_hex_column", type: "color", nullok: false } - ], - inputs: [ - { - "title": "new title 1", "website": "https://example1.com", "category": {index: 0, value: "Hotel"}, - "rating": "1", "summary": "This is the summary of this column 1.", "description": "## Description 1", - "json_col": JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "no_of_rooms": "1", "opened_on": moment("2017-01-01 01:01:01", "YYYY-MM-DD hh:mm:ss"), "date_col": "2017-01-01", "luxurious": false, - "text_array": ["v1", "v2"], "boolean_array": [true], "int4_array": [1, 2], "float4_array": [1, 2.2], - "date_array": ["2001-01-01", "2002-02-02"], "timestamp_array": ["2001-01-01T01:01:01"], - "timestamptz_array": ["2001-01-01T01:01:01-08:00"], - "color_rgb_hex_column": "#123456" - }, - { - "title": "new title 2", "website": "https://example2.com", "category": {index: 1, value: "Ranch"}, - "rating": "2", "summary": "This is the summary of this column 2.", "description": "## Description 2", - "json_col": JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "no_of_rooms": "2", "opened_on": moment("2017-02-02 02:02:02", "YYYY-MM-DD hh:mm:ss"), "date_col": "2017-02-02", "luxurious": true, - "text_array": ["v2", "v3"], "boolean_array": [false], "int4_array": [1, 2], "float4_array": [2, 3.3], - "date_array": ["2002-02-02"], "timestamp_array": ["2002-02-02T02:02:02"], - "timestamptz_array": ["2002-02-02T02:02:02-08:00"], - "color_rgb_hex_column": "#654321" - } - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "title", "website", "product-add_fk_category", "rating", "summary", "description", - "json_col", "no_of_rooms", "opened_on", "date_col", "luxurious", - "text_array", "boolean_array", "int4_array", "float4_array", "date_array", "timestamp_array", "timestamptz_array", "color_rgb_hex_column" - ], - results: [ - [ - "new title 1", {"link":"https://example1.com/", "value":"Link to Website"}, {"link":`${process.env.CHAISE_BASE_URL}/record/#${process.env.catalogId}/product-add:category/term=Hotel`, "value":"Hotel"}, - "1.0000", "This is the summary of this column 1.", "Description 1", JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "1", "2017-01-01 01:01:01", "2017-01-01", "false", - "v1, v2", "true", "1, 2", "1.0000, 2.2000", "2001-01-01, 2002-02-02", "2001-01-01 01:01:01", "2001-01-01 01:01:01", "#123456" - ], - [ - "new title 2", {"link":"https://example2.com/", "value":"Link to Website"}, {"link":`${process.env.CHAISE_BASE_URL}/record/#${process.env.catalogId}/product-add:category/term=Ranch`, "value":"Ranch"}, - "2.0000", "This is the summary of this column 2.", "Description 2", JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "2", "2017-02-02 02:02:02", "2017-02-02", "true", - "v2, v3", "false", "1, 2", "2.0000, 3.3000", "2002-02-02", "2002-02-02 02:02:02", "2002-02-02 02:02:02", "#654321" - ] - ], - files: [] - }, - { - comment: "uploading new files", - schema_name: "product-add", - table_name: "file", - table_displayname: "file", - table_comment: "asset/object", - test_results: !process.env.CI, - primary_keys: ["id"], - columns: [ - { name: "fileid", title: "fileid", type: "int4", skipValidation: true }, - { name: "uri", title: "uri", type: "text", isFile: true, comment: "asset/reference", skipValidation: true }, - { name: "timestamp_txt", title: "timestamp_txt", type: "text", skipValidation: true}, - ], - inputs: [ - {"fileid": "1", "uri": 0, "timestamp_txt": currentTimestampTime}, - {"fileid": "2", "uri": 1, "timestamp_txt": currentTimestampTime}, - {"fileid": "3", "uri": 2, "timestamp_txt": currentTimestampTime, validate: true} - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "fileid", "uri", "filename", "bytes" - ], - results: [ - ["1", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/1/.txt/3a8c740953a168d9761d0ba2c9800475:", "value": "testfile1MB.txt"}, "testfile1MB.txt", "1.02 MB"], - ["2", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:", "value": "testfile500kb.png"}, "testfile500kb.png", "512 kB"] - ], - files : [{ - name: "testfile1MB.txt", - size: "1024000", - displaySize: "1000 kB", - path: "testfile1MB.txt", - skipDeletion: true, - tooltip: "- testfile1MB.txt\n- 1000 kB" - }, { - name: "testfile500kb.png", - size: "512000", - displaySize: "500 kB", - path: "testfile500kb.png", - skipDeletion: true, - tooltip: "- testfile500kb.png\n- 500 kB" - }, { - name: "testfile5MB.txt", - size: "5242880", - displaySize: "5000 kB", - path: "testfile5MB.txt", - tooltip: "- testfile5MB.txt\n- 5 MB" - }] - }, - { - comment: "uploader when one file exists in hatrac and the other one is new", - schema_name: "product-add", - table_name: "file", - table_displayname: "file", - table_comment: "asset/object", - test_results: !process.env.CI, - primary_keys: ["id"], - columns: [ - { name: "fileid", title: "fileid", type: "int4", skipValidation: true }, - { name: "uri", title: "uri", type: "text", isFile: true, comment: "asset/reference", skipValidation: true }, - { name: "timestamp_txt", title: "timestamp_txt", type: "text", skipValidation: true}, - ], - inputs: [ - {"fileid": "1", "uri": 0, "timestamp_txt": currentTimestampTime}, // this is a new file - {"fileid": "2", "uri": 1, "timestamp_txt": currentTimestampTime}, // the uploaded file for this already exists (uploaded in the previou step) - {"fileid": "3", "uri": 1, "timestamp_txt": currentTimestampTime} // this form won't be submitted - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "fileid", "uri", "filename", "bytes" - ], - results: [ - ["1", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/1/.txt/b5dad28809685d9764dbd08fa23600bc:", "value": "testfile10MB_new.txt"}, "testfile10MB_new.txt", "10.2 MB"], - ["2", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:", "value": "testfile500kb.png"}, "testfile500kb.png", "512 kB"] - ], - files : [{ - name: "testfile10MB_new.txt", // a new file with new md5 - size: "10240000", - displaySize: "9.77 MB", - path: "testfile10MB_new.txt", - tooltip: "- testfile10MB_new.txt\n- 9.77 MB" - }, { - name: "testfile500kb.png", // using the same file that has been already uploaded - skipCreation: true, - skipDeletion: true, - size: "512000", - displaySize: "500 kB", - path: "testfile500kb.png", - tooltip: "- testfile500kb.png\n- 500 kB" - }] - }, - { - comment: "uploader when all the files already exist in hatrac", - schema_name: "product-add", - table_name: "file", - table_displayname: "file", - table_comment: "asset/object", - test_results: !process.env.CI, - primary_keys: ["id"], - columns: [ - { name: "fileid", title: "fileid", type: "int4", skipValidation: true }, - { name: "uri", title: "uri", type: "text", isFile: true, comment: "asset/reference", skipValidation: true }, - { name: "timestamp_txt", title: "timestamp_txt", type: "text", skipValidation: true}, - ], - inputs: [ - {"fileid": "1", "uri": 0, "timestamp_txt": currentTimestampTime}, // the uploaded file for this already exists (uploaded in the previou step) - {"fileid": "2", "uri": 1, "timestamp_txt": currentTimestampTime}, // the uploaded file for this already exists (uploaded in the previou step) - {"fileid": "3", "uri": 1, "timestamp_txt": currentTimestampTime} // this form won't be submitted - ], - formsOnLoad: 1, - formsAfterInput: 3, - result_columns: [ - "fileid", "uri", "filename", "bytes" - ], - results: [ - ["1", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/1/.txt/3a8c740953a168d9761d0ba2c9800475:", "value": "testfile1MB.txt"}, "testfile1MB.txt", "1.02 MB"], - ["2", {"link": "/hatrac/js/chaise/" + currentTimestampTime + "/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:", "value": "testfile500kb.png"}, "testfile500kb.png", "512 kB"] - ], - files : [{ - name: "testfile1MB.txt", // using the same file that has been already uploaded - skipCreation: true, - size: "1024000", - displaySize: "1000 kB", - path: "testfile1MB.txt", - tooltip: "- testfile1MB.txt\n- 1000 kB" - }, { - name: "testfile500kb.png", // using the same file that has been already uploaded - skipCreation: true, - size: "512000", - displaySize: "500 kB", - path: "testfile500kb.png", - tooltip: "- testfile500kb.png\n- 500 kB" - }] - } - ] -}; - -// keep track of namespaces that we use, so we can delete them afterwards -if (!process.env.CI) { - testConfiguration.hatracNamespaces.push(process.env.ERMREST_URL.replace("/ermrest", "") + "/hatrac/js/chaise/" + currentTimestampTime); -} - -describe('Record Add', function() { - - for (var i=0; i< testParams.tables.length; i++) { - (function(tableParams, index) { - - describe("======================================================================= \n " - + tableParams.inputs.length + " record(s) for table " + tableParams.table_name + " testing " + tableParams.comment + ",", function() { - - beforeAll(function () { - uri = browser.params.url + "/recordedit/#" + browser.params.catalogId + "/" + tableParams.schema_name + ":" + tableParams.table_name; - // if it's the same table-name, we have to reload. browser.get is not reloading the page - if (index > 0 && tableParams.table_name === testParams.tables[index-1].table_name) { - chaisePage.refresh(uri); - } else { - chaisePage.navigate(uri); - } - chaisePage.recordeditPageReady(); - }); - - describe("Presentation and validation,", function() { - - if (!process.env.CI && tableParams.files.length > 0) { - beforeAll(function() { - // create files that will be uploaded - recordEditHelpers.createFiles(tableParams.files); - console.log("\n"); - }); - } - - recordEditHelpers.testPresentationAndBasicValidation(tableParams, false); - }); - - describe("remove record, ", function() { - - if (tableParams.inputs.length > 1) { - - if (tableParams.files.length == 0) { - it("should click and add an extra record.", function(done) { - chaisePage.clickButton(chaisePage.recordEditPage.getCloneFormInputSubmitButton()).then(function () { - done(); - }).catch(chaisePage.catchTestError(done)); - }); - } - - it((tableParams.formsAfterInput) + " buttons should be visible and enabled", function(done) { - chaisePage.recordEditPage.getAllDeleteRowButtons().then(function(buttons) { - expect(buttons.length).toBe(tableParams.formsAfterInput); - buttons.forEach(function(btn) { - expect(btn.isDisplayed()).toBe(true); - expect(btn.isEnabled()).toBe(true); - }); - - done(); - }).catch(chaisePage.catchTestError(done)); - }); - - it("should click and remove the last record", function(done) { - chaisePage.recordEditPage.getAllDeleteRowButtons().then(function(buttons) { - // NOTE: chaisePage.clickButton() was clicking the submit button instead of the remove form button - // if this keeps happening after this change, scroll button into view then click - return buttons[tableParams.formsAfterInput - 1].click(); - }).then(function () { - return chaisePage.recordEditPage.getAllDeleteRowButtons(); - }).then(function(buttons) { - expect(buttons.length).toBe(tableParams.formsAfterInput - 1); - done() - }).catch(chaisePage.catchTestError(done)); - }); - } else { - it("zero delete buttons should be visible", function() { - chaisePage.recordEditPage.getAllDeleteRowButtons().then(function(buttons) { - expect(buttons.length).toBe(1); - buttons.forEach(function(btn) { - expect(btn.isDisplayed()).toBe(false); - }); - }); - }); - } - }); - - describe("Submit " + tableParams.inputs.length + " records", function() { - recordEditHelpers.testSubmission(tableParams); - }); - - if (!process.env.CI && tableParams.files.length > 0) { - afterAll(function() { - recordEditHelpers.deleteFiles(tableParams.files); - console.log("\n"); - }); - } - }); - })(testParams.tables[i], i); - } - - describe('When url has a prefill query string param set, ', function() { - beforeAll(function() { - var dataRow = browser.params.entities['product-add']['category'].find(function (entity) { - return entity.id == 10007; - }); - - chaisePage.navigate(browser.params.url + "/record/#" + browser.params.catalogId + "/product-add:category/RID=" + dataRow.RID) - chaisePage.recordPageReady(); - }); - - it('should pre-fill fields from the prefill cookie', function(done) { - var addBtn = element(by.css('#rt-heading-Accommodations .add-records-link')) - var allWindows; - - chaisePage.waitForClickableElement(addBtn).then(() => { - return addBtn.click(); - }).then(() => { - return browser.getAllWindowHandles(); - }).then((handles) => { - allWindows = handles - return browser.switchTo().window(allWindows[1]); - }).then(() => { - return chaisePage.recordeditPageReady(); - }).then(() => { - var field = chaisePage.recordEditPage.getForeignKeyInputDisplay('Category', 1); - expect(field.getText()).toBe('Castle'); - - // - Go back to initial Record page - browser.close(); - return browser.switchTo().window(allWindows[0]); - }).then(() => { - - done(); - }).catch((error) => { - done.fail(error); - }); - }); - }); - - describe('Markdown Editor Help button is clicked, ', function() { - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/product-add:accommodation"); - helpBtn = element.all(by.css('button[title=Help]')).get(0); - chaisePage.waitForElement(helpBtn); - }); - - it("should open a new window with the help page.",function(done){ - helpBtn.click().then(function () { - return browser.getAllWindowHandles(); - }).then(function(handles) { - allWindows = handles; - return browser.switchTo().window(allWindows[1]); - }).then(function() { - return chaisePage.waitForElement(element(by.id("mainTable"))); - }).then(function() { - // this is a static page, we just want to make sure this is the correct page. - // we don't need to test every element. - expect(element(by.id('mainTable')).all(by.tagName('tr')).count()).toBe(22,'Table row count could not be matched.'); - expect(element(by.id('rBold1')).getText()).toBe("**Something Bold**",'first row, first column missmatch'); - expect(element(by.id('rBold2')).getText()).toBe("__Something Bold__",'first row, second column missmatch'); - expect(element(by.id('oBold')).getAttribute('innerHTML')).toBe("Something Bold",'first row, third column missmatch'); - }).then(function() { - // - Go back to initial Record page - browser.close(); - browser.switchTo().window(allWindows[0]); - done(); - }).catch(function(error) { - done.fail(error); - }); - }); - }); -}); diff --git a/test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts b/test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts new file mode 100644 index 000000000..004ba3908 --- /dev/null +++ b/test/e2e/specs/all-features-confirmation/recordedit/add.spec.ts @@ -0,0 +1,340 @@ +import { test, expect } from '@playwright/test'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +import { deleteHatracNamespaces, getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; +import { + createFiles, deleteFiles, testFormPresentationAndValidation, + TestFormPresentationAndValidation, testSubmission, + TestSubmissionParams +} from '@isrd-isi-edu/chaise/test/e2e/utils/recordedit-utils'; +import moment from 'moment'; + +const currentTimestampTimeStr = moment().format('x'); + +const testFiles = [ + { + name: 'testfile1MB_add.txt', + size: '1024000', + path: 'testfile1MB_add.txt', + tooltip: '- testfile1MB_add.txt\n- 1000 kB' + }, + { + name: 'testfile500kb_add.png', + size: '512000', + path: 'testfile500kb_add.png', + tooltip: '- testfile500kb_add.png\n- 500 kB' + }, + { + name: 'testfile10MB_add.txt', + size: '10240000', + path: 'testfile10MB_add.txt', + tooltip: '- testfile10MB_add.txt\n- 9.77 MB' + }, +]; + +// we're testing this table multiple times +const fileTableDefaultPresentationProps = { + schemaName: 'product-add', + tableName: 'file', + tableDisplayname: 'file', + tableComment: 'asset/object', + columns: [ + { name: 'fileid', displayname: 'fileid', type: RecordeditInputType.INT_4, skipValidation: true }, + { name: 'uri', displayname: 'uri', type: RecordeditInputType.FILE, comment: 'asset/reference' }, + { name: 'timestamp_txt', displayname: 'timestamp_txt', type: RecordeditInputType.TEXT, skipValidation: true } + ] +} + +const testParams: { + tables: { + num_files: number, + presentation: TestFormPresentationAndValidation, + submission: TestSubmissionParams + }[] +} = { + tables: [ + { + num_files: 0, + presentation: { + description: 'multi create', + schemaName: 'product-add', + tableName: 'accommodation', + tableDisplayname: 'Accommodations', + tableComment: 'List of different types of accommodations', + columns: [ + { name: 'id', displayname: 'Id', type: RecordeditInputType.INT_4, disabled: true }, + { name: 'title', displayname: 'Name of Accommodation', type: RecordeditInputType.TEXT, isRequired: true }, + { name: 'website', displayname: 'Website', type: RecordeditInputType.TEXT, comment: 'A valid url of the accommodation' }, + { + name: 'category', displayname: 'Category', type: RecordeditInputType.FK_POPUP, isRequired: true, + comment: 'Type of accommodation (Resort, Hotel, or Motel)' + }, + { name: 'rating', displayname: 'User Rating', type: RecordeditInputType.NUMBER, isRequired: true }, + { + name: 'summary', displayname: 'Summary', type: RecordeditInputType.LONGTEXT, isRequired: true, + inlineComment: 'A comment displayed for the summary column' + }, + { name: 'description', displayname: 'Description', type: RecordeditInputType.MARKDOWN }, + { name: 'json_col', displayname: 'json_col', type: RecordeditInputType.JSON }, + { + name: 'no_of_rooms', displayname: 'Number of Rooms', type: RecordeditInputType.INT_2, + inlineComment: 'This shows the number of rooms!' + }, + { name: 'opened_on', displayname: 'Operational Since', type: RecordeditInputType.TIMESTAMP, isRequired: true }, + { name: 'date_col', displayname: 'date_col', type: RecordeditInputType.DATE }, + { name: 'luxurious', displayname: 'Is Luxurious', type: RecordeditInputType.BOOLEAN, isRequired: true }, + { name: 'text_array', displayname: 'text_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TEXT }, + { name: 'boolean_array', displayname: 'boolean_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.BOOLEAN }, + { name: 'int4_array', displayname: 'int4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.INT_4 }, + { name: 'float4_array', displayname: 'float4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.NUMBER }, + { name: 'date_array', displayname: 'date_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.DATE }, + { + name: 'timestamp_array', displayname: 'timestamp_array', + type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TIMESTAMP + }, + { + name: 'timestamptz_array', displayname: 'timestamptz_array', + type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TIMESTAMP + }, + { name: 'color_rgb_hex_column', displayname: 'color_rgb_hex_column', type: RecordeditInputType.COLOR, isRequired: true }, + ], + inputs: [ + { + 'title': 'new title 1', 'website': 'https://example1.com', 'category': { modal_num_rows: 5, modal_option_index: 0, rowName: 'Hotel' }, + 'rating': '1', 'summary': 'This is the summary of this column 1.', 'description': '## Description 1', + 'json_col': JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), + 'no_of_rooms': '1', 'opened_on': { date_value: '2017-01-01', time_value: '01:01:01' }, 'date_col': '2017-01-01', 'luxurious': 'false', + 'text_array': ['v1', 'v2'], 'boolean_array': ['true'], + 'int4_array': ['1', '2'], 'float4_array': ['1', '2.2'], + 'date_array': ['2001-01-01', '2002-02-02'], 'timestamp_array': [{ date_value: '2001-01-01', time_value: '01:01:01' }], + 'timestamptz_array': [{ date_value: '2001-01-01', time_value: '01:01:01' }], + 'color_rgb_hex_column': '#123456' + }, + { + 'title': 'new title 2', 'website': 'https://example2.com', 'category': { modal_num_rows: 5, modal_option_index: 1, rowName: 'Ranch' }, + 'rating': '2', 'summary': 'This is the summary of this column 2.', 'description': '## Description 2', + 'json_col': JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), + 'no_of_rooms': '2', 'opened_on': { date_value: '2017-02-02', time_value: '02:02:02' }, 'date_col': '2017-02-02', 'luxurious': 'true', + 'text_array': ['v2', 'v3'], 'boolean_array': ['false'], 'int4_array': ['1', '2'], 'float4_array': ['2', '3.3'], + 'date_array': ['2002-02-02'], 'timestamp_array': [{ date_value: '2002-02-02', time_value: '02:02:02' }], + 'timestamptz_array': [{ date_value: '2002-02-02', time_value: '02:02:02' }], + 'color_rgb_hex_column': '#654321' + } + ] + }, + submission: { + tableDisplayname: 'Accommodations', + resultColumnNames: [ + 'title', 'website', 'product-add_fk_category', 'rating', 'summary', 'description', + 'json_col', 'no_of_rooms', 'opened_on', 'date_col', 'luxurious', + 'text_array', 'boolean_array', 'int4_array', 'float4_array', 'date_array', 'timestamp_array', 'timestamptz_array', 'color_rgb_hex_column' + ], + resultRowValues: [ + [ + 'new title 1', { url: 'https://example1.com', caption: 'Link to Website' }, + { url: '/product-add:category/term=Hotel', caption: 'Hotel' }, + '1.0000', 'This is the summary of this column 1.', 'Description 1', + JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), + '1', '2017-01-01 01:01:01', '2017-01-01', 'false', + 'v1, v2', 'true', '1, 2', '1.0000, 2.2000', '2001-01-01, 2002-02-02', '2001-01-01 01:01:01', '2001-01-01 01:01:01', + '#123456' + ], + [ + 'new title 2', { url: 'https://example2.com', caption: 'Link to Website' }, + { url: '/product-add:category/term=Ranch', caption: 'Ranch' }, + '2.0000', 'This is the summary of this column 2.', 'Description 2', + JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), + '2', '2017-02-02 02:02:02', '2017-02-02', 'true', + 'v2, v3', 'false', '1, 2', '2.0000, 3.3000', '2002-02-02', '2002-02-02 02:02:02', '2002-02-02 02:02:02', + '#654321' + ] + ] + } + }, + { + num_files: 2, // only two forms will be submitted + presentation: { + ...fileTableDefaultPresentationProps, + description: 'multi create with new files', + inputs: [ + { 'fileid': '1', 'uri': testFiles[0], 'timestamp_txt': currentTimestampTimeStr }, + { 'fileid': '2', 'uri': testFiles[1], 'timestamp_txt': currentTimestampTimeStr }, + { 'fileid': '3', 'uri': testFiles[1], 'timestamp_txt': currentTimestampTimeStr } // the form will be removed + ] + }, + submission: { + tableDisplayname: 'file', + resultColumnNames: ['fileid', 'uri', 'filename', 'bytes'], + resultRowValues: [ + [ + '1', + { caption: 'testfile1MB_add.txt', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/1/.txt/3a8c740953a168d9761d0ba2c9800475:` }, + 'testfile1MB_add.txt', + '1.02 MB' + ], + [ + '2', + { caption: 'testfile500kb_add.png', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:` }, + 'testfile500kb_add.png', + '512 kB' + ] + ] + } + }, + { + num_files: 2, // only two forms will be submitted + presentation: { + description: 'multi create when one file previously uploaded to hatrac', + schemaName: 'product-add', + tableName: 'file', + tableDisplayname: 'file', + tableComment: 'asset/object', + columns: [ + { name: 'fileid', displayname: 'fileid', type: RecordeditInputType.INT_4, skipValidation: true }, + { name: 'uri', displayname: 'uri', type: RecordeditInputType.FILE, comment: 'asset/reference' }, + { name: 'timestamp_txt', displayname: 'timestamp_txt', type: RecordeditInputType.TEXT, skipValidation: true } + ], + inputs: [ + { 'fileid': '1', 'uri': testFiles[2], 'timestamp_txt': currentTimestampTimeStr }, // this is new + { 'fileid': '2', 'uri': testFiles[1], 'timestamp_txt': currentTimestampTimeStr }, // this is uploaded in the previous test + { 'fileid': '3', 'uri': testFiles[1], 'timestamp_txt': currentTimestampTimeStr } // the form will be removed + ] + }, + submission: { + tableDisplayname: 'file', + resultColumnNames: ['fileid', 'uri', 'filename', 'bytes'], + resultRowValues: [ + [ + '1', + { caption: 'testfile10MB_add.txt', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/1/.txt/b5dad28809685d9764dbd08fa23600bc:` }, + 'testfile10MB_add.txt', + '10.2 MB' + ], + [ + '2', + { caption: 'testfile500kb_add.png', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:` }, + 'testfile500kb_add.png', + '512 kB' + ] + ] + } + }, + { + num_files: 2, // only two forms will be submitted + presentation: { + description: 'multi create when both files previously uploaded to hatrac', + schemaName: 'product-add', + tableName: 'file', + tableDisplayname: 'file', + tableComment: 'asset/object', + columns: [ + { name: 'fileid', displayname: 'fileid', type: RecordeditInputType.INT_4, skipValidation: true }, + { name: 'uri', displayname: 'uri', type: RecordeditInputType.FILE, comment: 'asset/reference' }, + { name: 'timestamp_txt', displayname: 'timestamp_txt', type: RecordeditInputType.TEXT, skipValidation: true } + ], + inputs: [ + { 'fileid': '1', 'uri': testFiles[0], 'timestamp_txt': currentTimestampTimeStr }, // this is uploaded in the previous test + { 'fileid': '2', 'uri': testFiles[1], 'timestamp_txt': currentTimestampTimeStr }, // this is uploaded in the previous test + { 'fileid': '3', 'uri': testFiles[1], 'timestamp_txt': currentTimestampTimeStr } // the form will be removed + ] + }, + submission: { + tableDisplayname: 'file', + resultColumnNames: ['fileid', 'uri', 'filename', 'bytes'], + resultRowValues: [ + [ + '1', + { caption: 'testfile1MB_add.txt', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/1/.txt/3a8c740953a168d9761d0ba2c9800475:` }, + 'testfile1MB_add.txt', + '1.02 MB' + ], + [ + '2', + { caption: 'testfile500kb_add.png', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/2/.png/2ada69fe3cdadcefddc5a83144bddbb4:` }, + 'testfile500kb_add.png', + '512 kB' + ] + ] + } + } + ] +} + + +test.describe('Recordedit create', () => { + /** + * we have to make sure we're running test cases sequentially since upload test cases are testing that users + * can upload the same file to the same location. That's why we're not running tests here in parallel + */ + + test.beforeAll(async () => { + await createFiles(testFiles); + }); + + for (const params of testParams.tables) { + const presentation = params.presentation; + + test(`${presentation.description}`, async ({ page, baseURL }, testInfo) => { + const numForms = presentation.inputs.length; + + await test.step('open recordedit page', async () => { + const url = `${baseURL}/recordedit/#${getCatalogID(testInfo.project.name)}/${presentation.schemaName}:${presentation.tableName}`; + await page.goto(url); + await RecordeditLocators.waitForRecordeditPageReady(page); + }); + + if (numForms > 1) { + await test.step('clone more forms', async () => { + let cnt = 1; + while (cnt < numForms) { + await RecordeditLocators.getCloneFormInputSubmitButton(page).click(); + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(++cnt); + } + }); + } + + // test everything related to the form + await testFormPresentationAndValidation(page, baseURL, testInfo, presentation, false); + + // this test clones the form and then remove the extra one. just to make sure nothing is affected by this. + await test.step('remove form', async () => { + // for the tests with file, we want to remove the actual form instead of adding a new one. + const addExtraForm = params.num_files === 0; + + if (numForms === 1) { + await test.step('remove button should not be offered when only one form is visible', async () => { + await expect.soft(RecordeditLocators.getAllDeleteRowButtons(page)).toHaveCount(0); + }); + } + + if (addExtraForm) { + await test.step('clone an extra form', async () => { + await RecordeditLocators.getCloneFormInputSubmitButton(page).click(); + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(numForms + 1); + await expect.soft(RecordeditLocators.getAllDeleteRowButtons(page)).toHaveCount(numForms + 1); + }); + } + + await test.step('remove the last form', async () => { + const cnt = addExtraForm ? numForms : numForms - 1; + await RecordeditLocators.getDeleteRowButton(page, cnt).click(); + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(cnt); + await expect.soft(RecordeditLocators.getAllDeleteRowButtons(page)).toHaveCount(cnt === 1 ? 0 : cnt); + }); + }); + + await test.step('submit and save the data', async () => { + let timeout: number | undefined; + if (params.num_files > 0) { + timeout = params.num_files * 30 * 1000; + } + await testSubmission(page, params.submission, false, timeout); + }); + }); + } + + test.afterAll(async () => { + await deleteFiles(testFiles); + await deleteHatracNamespaces([`/hatrac/js/chaise/${currentTimestampTimeStr}`]); + }); + +}); diff --git a/test/e2e/specs/all-features-confirmation/recordedit/delete.spec.js b/test/e2e/specs/all-features-confirmation/recordedit/delete.spec.js deleted file mode 100644 index 42ce1fe9b..000000000 --- a/test/e2e/specs/all-features-confirmation/recordedit/delete.spec.js +++ /dev/null @@ -1,66 +0,0 @@ -const chaisePage = require('../../../utils/chaise.page.js'); -const EC = protractor.ExpectedConditions; - -describe('Bulk delete in recordedit,', function () { - - beforeAll((done) => { - const baseURL = browser.params.url + '/recordedit/#' + browser.params.catalogId + '/product-delete:accommodation'; - chaisePage.navigate(baseURL + '/id=2000;id=4004').then(() => { - return chaisePage.recordeditPageReady(); - }).then(() => { - done(); - }).catch(chaisePage.catchTestError(done)); - }); - - it('Bulk delete button should be present and user should be able to click it.', (done) => { - const bulkDeleteBtn = chaisePage.recordEditPage.getBulkDeleteButton(); - const confirmModalTitle = chaisePage.recordPage.getConfirmDeleteTitle(); - expect(bulkDeleteBtn.isDisplayed()).toBeTruthy(); - bulkDeleteBtn.click().then(() => { - - browser.wait(EC.visibilityOf(confirmModalTitle), browser.params.defaultTimeout); - - const confirmButton = chaisePage.recordPage.getConfirmDeleteButton(); - browser.wait(EC.visibilityOf(confirmButton), browser.params.defaultTimeout); - - expect(chaisePage.recordPage.getConfirmDeleteModalText().getText()).toBe('Are you sure you want to delete all 2 of the displayed records?'); - - return chaisePage.clickButton(confirmButton); - }).then(() => { - done(); - }).catch(chaisePage.catchTestError(done)); - }); - - it('After the delete is done, user should see the proper message', (done) => { - const batchDeleteSummary = chaisePage.errorModal.getElement(); - const summaryTitle = chaisePage.errorModal.getTitle(); - - chaisePage.waitForElement(batchDeleteSummary).then(() => { - return chaisePage.waitForElement(summaryTitle); - }).then(() => { - expect(summaryTitle.getText()).toEqual('Batch Delete Summary', 'title missmatch'); - const expectedBody = [ - 'All of the 2 displayed records successfully deleted.', - '\nClick OK to go to the Recordset.', - ].join('\n'); - expect(chaisePage.errorModal.getBody().getText()).toBe(expectedBody, 'body missmatch'); - done(); - }).catch(chaisePage.catchTestError(done)); - }); - - it('clicking on "ok" button should redirect users to recordset page', (done) => { - const summaryCloseBtn = chaisePage.errorModal.getOKButton(); - browser.wait(EC.elementToBeClickable(summaryCloseBtn), browser.params.defaultTimeout).then(() => { - return summaryCloseBtn.click(); - }).then(() => { - // wait for url change - browser.wait(function () { - return browser.driver.getCurrentUrl().then(function (url) { - return url.startsWith(process.env.CHAISE_BASE_URL + "/recordset/"); - }); - }, browser.params.defaultTimeout); - done(); - }).catch(chaisePage.catchTestError(done)); - }); - -}); diff --git a/test/e2e/specs/all-features-confirmation/recordedit/delete.spec.ts b/test/e2e/specs/all-features-confirmation/recordedit/delete.spec.ts new file mode 100644 index 000000000..ae4cef11e --- /dev/null +++ b/test/e2e/specs/all-features-confirmation/recordedit/delete.spec.ts @@ -0,0 +1,47 @@ +import { test, expect } from '@playwright/test'; +import RecordeditLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +import ModalLocators from '@isrd-isi-edu/chaise/test/e2e/locators/modal'; + +import { getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; + +test('Bulk delete in recordedit', async ({ page, baseURL }, testInfo) => { + + await test.step('navigate to recordedit page', async () => { + const url = `${baseURL}/recordedit/#${getCatalogID(testInfo.project.name)}/product-delete:accommodation/id=2000;id=4004`; + await page.goto(url); + await RecordeditLocators.waitForRecordeditPageReady(page); + }); + + await test.step('Bulk delete button should be present and user should be able to click it.', async () => { + const confirmModal = ModalLocators.getConfirmDeleteModal(page); + const modalOKBtn = ModalLocators.getOkButton(confirmModal); + const deleteBtn = RecordeditLocators.getBulkDeleteButton(page); + + await expect.soft(deleteBtn).toBeVisible(); + await deleteBtn.click(); + await expect.soft(confirmModal).toBeVisible(); + + await expect.soft(ModalLocators.getModalText(confirmModal)).toHaveText('Are you sure you want to delete all 2 of the displayed records?'); + await expect.soft(modalOKBtn).toHaveText('Delete'); + await modalOKBtn.click(); + await expect.soft(confirmModal).not.toBeAttached(); + }); + + await test.step('After the delete is done, user should see the proper message', async () => { + const summaryModal = ModalLocators.getErrorModal(page); + await expect.soft(summaryModal).toBeVisible(); + await expect.soft(ModalLocators.getModalTitle(summaryModal)).toHaveText('Batch Delete Summary'); + + const expectedBody = [ + 'All of the 2 displayed records successfully deleted.', + 'Click OK to go to the Recordset.' + ].join(''); + await expect.soft(ModalLocators.getModalText(summaryModal)).toHaveText(expectedBody); + }); + + await test.step('clicking on "ok" button should redirect users to recordset page', async () => { + await ModalLocators.getOkButton(ModalLocators.getErrorModal(page)).click(); + await page.waitForURL('**/recordset/**'); + }); + +}) diff --git a/test/e2e/specs/all-features-confirmation/recordedit/edit-delete.conf.js b/test/e2e/specs/all-features-confirmation/recordedit/edit-delete.conf.js deleted file mode 100644 index 7765f3ab3..000000000 --- a/test/e2e/specs/all-features-confirmation/recordedit/edit-delete.conf.js +++ /dev/null @@ -1,16 +0,0 @@ -var pConfig = require('./../../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - configFileName: 'recordedit/edit.dev.json', - specs: [ - "edit.spec.js", - "delete.spec.js", - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - }, - chaiseConfigFilePath: 'test/e2e/specs/all-features-confirmation/chaise-config.js' -}); - -exports.config = config; diff --git a/test/e2e/specs/all-features-confirmation/recordedit/edit-delete.config.ts b/test/e2e/specs/all-features-confirmation/recordedit/edit-delete.config.ts new file mode 100644 index 000000000..9c12c2cc2 --- /dev/null +++ b/test/e2e/specs/all-features-confirmation/recordedit/edit-delete.config.ts @@ -0,0 +1,11 @@ +import getConfig from '@isrd-isi-edu/chaise/test/e2e/setup/playwright.configuration'; + +export default getConfig({ + testName: 'all-features-confirmation/recordedit/edit-delete', + configFileName: 'recordedit/edit.dev.json', + mainSpecName: 'all-features-confirmation', + testMatch: [ + 'edit.spec.ts', + 'delete.spec.ts' + ] +}); diff --git a/test/e2e/specs/all-features-confirmation/recordedit/edit.spec.js b/test/e2e/specs/all-features-confirmation/recordedit/edit.spec.js deleted file mode 100644 index 93bc010f5..000000000 --- a/test/e2e/specs/all-features-confirmation/recordedit/edit.spec.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * This test case is for testing editing a single record - * - */ -var testConfiguration = browser.params.configuration; -var chaisePage = require('../../../utils/chaise.page.js'); -var recordEditHelpers = require('../../../utils/recordedit-helpers.js'); -var moment = require('moment'); - -var currentTimestampTime = moment().format("x"); -var testParams = { - tables: [{ - schema_name: "product-edit", - table_name: "accommodation", - record_displayname: "Sherathon Hotel", //since this is in single-edit, displayname is rowname. - table_displayname: "Accommodations", - table_comment: "List of different types of accommodations", - key: { name: "id", value: "2000", operator: "="}, - test_results: true, - primary_keys: ["id"], - columns: [ - { name: "id", generated: true, immutable: true, title: "Id", type: "serial4", nullok: false}, - { name: "title", title: "Name of Accommodation", type: "text", nullok: false}, - { name: "website", title: "Website", type: "text", comment: "A valid url of the accommodation"}, - { name: "category", title: "Category", type: "text", isForeignKey: true, count: 5, totalCount: 5, comment: "_markdown_ comment can be turned off", nullok: false}, // the total count is the total number of rows in the category.json data file - { name: "rating", title: "User Rating", type: "float4", nullok: false, inline_comment: "Average user rating from 1 to 5 stars"}, - { name: "summary", title: "Summary", nullok: false, type: "longtext"}, - { name: "description", title: "Description", type: "markdown"}, - { name: "json_col", title: "json_col", value:JSON.stringify({"name": "testing"},undefined,2) , type: "json" }, - { name: "no_of_rooms", title: "Number of Rooms", type: "int2"}, - { name: "opened_on", title: "Operational Since", type: "timestamptz", nullok: false, inline_comment: "The exact time and date where this accommodation became available!" }, - { name: "date_col", title: "date_col", type: "date"}, - { name: "luxurious", title: "Is Luxurious", type: "boolean" }, - { name: "text_array", title: "text_array", type: "array", baseType: "text", nullok:false }, - { name: "boolean_array", title: "boolean_array", type: "array", baseType: "boolean" }, - { name: "int4_array", title: "int4_array", type: "array", baseType: "integer" }, - { name: "float4_array", title: "float4_array", type: "array", baseType: "number" }, - { name: "date_array", title: "date_array", type: "array", baseType: "date" }, - { name: "timestamp_array", title: "timestamp_array", type: "array", baseType: "timestamp" }, - { name: "timestamptz_array", title: "timestamptz_array", type: "array", baseType: "timestamptz" }, - { name: "color_rgb_hex_column", title: "color_rgb_hex_column", type: "color"} - ], - values: [ - {"id": "2000", "title": "Sherathon Hotel", "website": "http://www.starwoodhotels.com/sheraton/index.html", "category": "Castle", "rating": "4.3", - "summary": "Sherathon Hotels is an international hotel company with more than 990 locations in 73 countries. The first Radisson Hotel was built in 1909 in Minneapolis, Minnesota, US. It is named after the 17th-century French explorer Pierre-Esprit Radisson.", - "description": "**CARING. SHARING. DARING.**", "json_col": null, "no_of_rooms": "23", "opened_on": moment("12/9/2008, 12:00:00 AM", "MM/DD/YYYY, HH:mm:ss A"), - "date_col": "2008-12-09", "luxurious": "true", - "text_array": ["v2","v3"], "boolean_array": [false], "int4_array": [1], "float4_array": [1.1,2.2], - "date_array": null, "timestamp_array": ["2003-03-03T03:03:03"], - "timestamptz_array": [moment("2002-02-02T02:02:02-08:00", "YYYY-MM-DDTHH:mm:ssZ").format("YYYY-MM-DDTHH:mm:ssZ")], - "color_rgb_hex_column": "#623456" - } - ], - inputs: [ - { - "title": "new title 1", "website": "https://example1.com", "category": {index: 1, value: "Ranch"}, - "rating": "1e0", "summary": "This is the summary of this column 1.", "description": "## Description 1", "json_col": JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "no_of_rooms": "1", "opened_on": moment("2017-01-01 01:01:01", "YYYY-MM-DD hh:mm:ss"), "date_col": "2017-01-01", "luxurious": false, - "text_array": ["v1", "v2"], "boolean_array": [true,false], "int4_array": [1, 2], "float4_array": [1, 2.2], - "date_array": ["2001-01-01", "2002-02-02"], "timestamp_array": ["2001-03-02T01:01:01" , "2001-01-01T01:01:01"], - "timestamptz_array": ["2001-01-01T01:01:01-08:00"], - "color_rgb_hex_column": "#723456" - } - ], - result_columns: [ - "title", "website", "product-edit_fk_category", "rating", "summary", "description", - "json_col", "no_of_rooms", "opened_on", "date_col", "luxurious", - "text_array", "boolean_array", "int4_array", "float4_array", "date_array", "timestamp_array", "timestamptz_array", - "color_rgb_hex_column" - ], - results: [ - [ - "new title 1", {"link":"https://example1.com/", "value":"Link to Website"}, - {"link":`${process.env.CHAISE_BASE_URL}/record/#${process.env.catalogId}/product-edit:category/id=10004`, "value":"Castle"}, - "1.0000", "This is the summary of this column 1.", "Description 1", JSON.stringify({"items": {"qty": 6,"product": "apple"},"customer": "Nitish Sahu"},undefined,2), - "1", "2017-01-01 01:01:01", "2017-01-01", "false", - "v1, v2", "true, false", "1, 2", "1.0000, 2.2000", "2001-01-01, 2002-02-02", "No value, 2001-01-01T01:01:01", "No value, 2001-01-01 01:01:01", "#723456" - ] - ], - files: [] - }, - { - schema_name: "product-edit", - table_name: "file", - record_displayname: "90008", //since this is in single-edit, displayname is rowname. - table_displayname: "file", - table_comment: "asset/object", - test_results: !process.env.CI, - primary_keys: ["id"], - key: { name: "id", value: "90008", operator: "="}, - columns: [ - { name: "fileid", title: "fileid", type: "int4" }, - { name: "uri", title: "uri", type: "text", isFile: true, comment: "asset/reference" }, - { name: "timestamp_txt", title: "timestamp_txt", type: "text"} - ], - values: [ - {"fileid":"","uri":"http://images.trvl-media.com/hotels/1000000/30000/28200/28110/28110_191_z.jpg"} - ], - inputs: [ - {"fileid": "4", "uri": 0, "timestamp_txt": currentTimestampTime} - ], - result_columns: [ - "fileid", "uri", "filename", "bytes" - ], - results: [ - ["4", {"link": "/hatrac/js/chaise/"+currentTimestampTime+"/4/.png/", "value": "testfile500kb.png"}, "testfile500kb.png", "512 kB"] - ], - files : [{ - name: "testfile500kb.png", - size: "512000", - displaySize: "500 kB", - path: "testfile500kb.png", - tooltip: "- testfile500kb.png\n- 500 kB" - }] - } - ] -}; - -if (!process.env.CI) { - // keep track of namespaces that we use, so we can delete them afterwards - testConfiguration.hatracNamespaces.push(process.env.ERMREST_URL.replace("/ermrest", "") + "/hatrac/js/chaise/" + currentTimestampTime); -} - -describe('Edit existing record,', function() { - - for (var i=0; i< testParams.tables.length; i++) { - - (function(tableParams) { - - if (!process.env.CI && tableParams.files.length > 0) { - beforeAll(function() { - // create files that will be uploaded - recordEditHelpers.createFiles(tableParams.files); - console.log("\n"); - }); - } - - describe("For table " + tableParams.table_name + ",", function() { - - beforeAll(function () { - - var keys = []; - keys.push(tableParams.key.name + tableParams.key.operator + tableParams.key.value); - chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/"+ tableParams.schema_name +":" + tableParams.table_name + "/" + keys.join("&")); - - chaisePage.waitForElement(element(by.id("submit-record-button"))); - }); - - - describe("Presentation and validation,", function() { - recordEditHelpers.testPresentationAndBasicValidation(tableParams, true); - }); - - describe("Submitting an existing record,", function() { - recordEditHelpers.testSubmission(tableParams, true); - }); - - if (!process.env.CI && tableParams.files.length > 0) { - afterAll(function(done) { - recordEditHelpers.deleteFiles(tableParams.files); - console.log("\n"); - done(); - }); - } - - }); - - })(testParams.tables[i]); - } -}); diff --git a/test/e2e/specs/all-features-confirmation/recordedit/edit.spec.ts b/test/e2e/specs/all-features-confirmation/recordedit/edit.spec.ts new file mode 100644 index 000000000..bdf88035f --- /dev/null +++ b/test/e2e/specs/all-features-confirmation/recordedit/edit.spec.ts @@ -0,0 +1,370 @@ +/* eslint-disable max-len */ +import { expect, test } from '@playwright/test'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +import { deleteHatracNamespaces, getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; +import { + createFiles, deleteFiles, testFormPresentationAndValidation, + TestFormPresentationAndValidation, testSubmission, + TestSubmissionParams +} from '@isrd-isi-edu/chaise/test/e2e/utils/recordedit-utils'; +import moment from 'moment'; + + +const currentTimestampTimeStr = moment().format('x'); + +const testFiles = [ + { + name: 'testfile500kb_edit.png', + size: '512000', + path: 'testfile500kb_edit.png', + tooltip: '- testfile500kb_edit.png\n- 500 kB' + }, + { + name: 'testfile1MB_edit.txt', + size: '1024000', + path: 'testfile1MB_edit.txt', + tooltip: '- testfile1MB_edit.txt\n- 1000 kB' + } +]; + +// we're testing this table multiple times +const accomodationDefaultPresentationProps = { + schemaName: 'product-edit', + tableName: 'accommodation', + tableDisplayname: 'Accommodations', + tableComment: 'List of different types of accommodations', + columns: [ + { name: 'id', displayname: 'Id', type: RecordeditInputType.INT_4, disabled: true }, + { name: 'title', displayname: 'Name of Accommodation', type: RecordeditInputType.TEXT, isRequired: true }, + { name: 'website', displayname: 'Website', type: RecordeditInputType.TEXT, comment: 'A valid url of the accommodation' }, + { + name: 'category', displayname: 'Category', type: RecordeditInputType.FK_POPUP, isRequired: true, + comment: '_markdown_ comment can be turned off' + }, + { + name: 'rating', displayname: 'User Rating', type: RecordeditInputType.NUMBER, isRequired: true, + inlineComment: 'Average user rating from 1 to 5 stars' + }, + { name: 'summary', displayname: 'Summary', type: RecordeditInputType.LONGTEXT, isRequired: true }, + { name: 'description', displayname: 'Description', type: RecordeditInputType.MARKDOWN }, + { name: 'no_of_rooms', displayname: 'Number of Rooms', type: RecordeditInputType.INT_4 }, + { + name: 'opened_on', displayname: 'Operational Since', type: RecordeditInputType.TIMESTAMP, isRequired: true, + inlineComment: 'The exact time and date where this accommodation became available!' + }, + { name: 'date_col', displayname: 'date_col', type: RecordeditInputType.DATE }, + { name: 'luxurious', displayname: 'Is Luxurious', type: RecordeditInputType.BOOLEAN }, + { name: 'json_col', displayname: 'json_col', type: RecordeditInputType.JSON }, + { name: 'text_array', displayname: 'text_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TEXT, isRequired: true }, + { name: 'boolean_array', displayname: 'boolean_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.BOOLEAN }, + { name: 'int4_array', displayname: 'int4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.INT_4 }, + { name: 'float4_array', displayname: 'float4_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.NUMBER }, + { name: 'date_array', displayname: 'date_array', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.DATE }, + { + name: 'timestamp_array', displayname: 'timestamp_array', + type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TIMESTAMP + }, + { + name: 'timestamptz_array', displayname: 'timestamptz_array', + type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TIMESTAMP + }, + { name: 'color_rgb_hex_column', displayname: 'color_rgb_hex_column', type: RecordeditInputType.COLOR }, + ] +}; + +const fileTableDefaultPresentationProps = { + schemaName: 'product-edit', + tableName: 'file', + tableDisplayname: 'file', + tableComment: 'asset/object', + columns: [ + { name: 'fileid', displayname: 'fileid', type: RecordeditInputType.INT_4, skipValidation: true }, + { name: 'uri', displayname: 'uri', type: RecordeditInputType.FILE, comment: 'asset/reference' }, + { name: 'timestamp_txt', displayname: 'timestamp_txt', type: RecordeditInputType.TEXT, skipValidation: true } + ] +} + +const testParams: { + tables: { + num_files: number, + filter: string + presentation: TestFormPresentationAndValidation, + submission: TestSubmissionParams + }[] +} = { + tables: [ + { + num_files: 0, + filter: 'id=2000', + presentation: { + description: 'single edit', + ...accomodationDefaultPresentationProps, + rowNames: ['Sherathon Hotel'], + values: [ + { + 'id': '2000', 'title': 'Sherathon Hotel', + 'website': 'http://www.starwoodhotels.com/sheraton/index.html', 'category': 'Hotel', 'rating': '4.3', + 'summary': 'Sherathon Hotels is an international hotel company with more than 990 locations in 73 countries. The first Radisson Hotel was built in 1909 in Minneapolis, Minnesota, US. It is named after the 17th-century French explorer Pierre-Esprit Radisson.', + 'description': '**CARING. SHARING. DARING.**', 'no_of_rooms': '23', 'opened_on': { date_value: '2008-12-09', time_value: '00:00:00' }, + 'date_col': '2008-12-09', 'luxurious': 'true', 'json_col': '', + 'text_array': ['v2', 'v3'], 'boolean_array': ['false'], 'int4_array': ['1'], 'float4_array': ['1.1', '2.2'], + 'date_array': '', 'timestamp_array': ['2003-03-03T03:03:03'], + 'timestamptz_array': [{ date_value: '2002-02-02', time_value: '02:02:02' }], + 'color_rgb_hex_column': '#623456' + } + ], + inputs: [ + { + 'title': 'new title 1', 'website': 'https://new-site.com', 'category': { modal_num_rows: 5, modal_option_index: 1, rowName: 'Ranch' }, + 'rating': '1e0', 'summary': 'This is the summary of this column 1.', 'description': '## Description 1', + 'json_col': JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), + 'no_of_rooms': '1', 'opened_on': { date_value: '2017-01-01', time_value: '01:01:01' }, 'date_col': '2017-01-01', 'luxurious': 'false', + 'text_array': ['v1', 'v2'], 'boolean_array': ['true', 'false'], + 'int4_array': ['1', '2', '3'], 'float4_array': ['1', '2.2'], + 'date_array': ['2001-01-01', '2002-02-02'], 'timestamp_array': [{ date_value: '2001-03-01', time_value: '01:01:01' }], + 'timestamptz_array': [{ date_value: '2001-01-01', time_value: '01:01:01' }, { date_value: '2004-04-04', time_value: '11:11:11' }], + 'color_rgb_hex_column': '#723456' + }, + ] + }, + submission: { + tableDisplayname: 'Accommodations', + resultColumnNames: [ // this is single edit, so these are record page columns + 'Name of Accommodation', 'Website', 'Category', 'User Rating', 'Summary', 'Description', 'Number of Rooms', + 'Operational Since', 'date_col', 'Is Luxurious', 'json_col', + 'text_array', 'boolean_array', 'int4_array', 'float4_array', 'date_array', 'timestamp_array', 'timestamptz_array', 'color_rgb_hex_column' + ], + resultRowValues: [ + [ + 'new title 1', { url: 'https://new-site.com', caption: 'Link to Website' }, { url: '/product-edit:category/', caption: 'Ranch' }, + '1.0000', 'This is the summary of this column 1.', 'Description 1', '1', '2017-01-01 01:01:01', '2017-01-01', 'false', + JSON.stringify({ 'items': { 'qty': 6, 'product': 'apple' }, 'customer': 'John Smith' }, undefined, 2), + 'v1, v2', 'true, false', '1, 2, 3', '1.0000, 2.2000', '2001-01-01, 2002-02-02', '2001-03-01 01:01:01', + '2001-01-01 01:01:01, 2004-04-04 11:11:11', '#723456' + ] + ] + } + }, + { + num_files: 0, + filter: 'id=2001;id=2004@sort(id)', + presentation: { + description: 'multi edit', + ...accomodationDefaultPresentationProps, + rowNames: ['Radisson Hotel', 'Super 8 North Hollywood Motel'], + values: [ + { + 'id': '2001', 'title': 'Radisson Hotel', + 'website': 'http://www.radisson.com/', 'category': 'Hotel', 'rating': '4.7', + 'summary': 'Radisson Hotels is an international hotel company with more than 990 locations in 73 countries. The first Radisson Hotel was built in 1909 in Minneapolis, Minnesota, US. It is named after the 17th-century French explorer Pierre-Esprit Radisson.', + 'description': '** CARING. SHARING. DARING.**\nRadisson^®^ is synonymous with outstanding levels of service and comfort delivered with utmost style. And today, we deliver even more to make sure we maintain our position at the forefront of the hospitality industry now and in the future.\nOur hotels are service driven, responsible, socially and locally connected and demonstrate a modern friendly attitude in everything we do. Our aim is to deliver our outstanding `Yes I Can!` ^SM^ service, comfort and style where you need us.\n\n**THE RADISSON^®^ WAY** Always positive, always smiling and always professional, Radisson people set Radisson apart. Every member of the team has a dedication to `Yes I Can!` ^SM^ hospitality – a passion for ensuring the total wellbeing and satisfaction of each individual guest. Imaginative, understanding and truly empathetic to the needs of the modern traveler, they are people on a special mission to deliver exceptional Extra Thoughtful Care.', + 'no_of_rooms': '46', 'opened_on': { date_value: '2002-01-22', time_value: '00:00:00' }, + 'date_col': '2008-12-09', 'luxurious': 'true', 'json_col': JSON.stringify({ 'name': 'testing_json' }, undefined, 2), + 'text_array': ['val1'], 'boolean_array': ['false', 'false'], 'int4_array': ['1', '2', '3', '4', '5'], 'float4_array': ['-5.2'], + 'date_array': '', 'timestamp_array': '', + 'timestamptz_array': '', 'color_rgb_hex_column': '#223456' + }, + { + 'id': '2004', 'title': 'Super 8 North Hollywood Motel', + 'website': 'https://www.kayak.com/hotels/Super-8-North-Hollywood-c31809-h40498/2016-06-09/2016-06-10/2guests', 'category': 'Motel', 'rating': '2.8', + 'summary': 'Fair Hotel. Close to Universal Studios. Located near shopping areas with easy access to parking. Professional staff and clean rooms. Poorly-maintained rooms.', + 'description': '** CARING. SHARING. DARING.**\nRadisson^®^ is synonymous with outstanding levels of service and comfort delivered with utmost style. And today, we deliver even more to make sure we maintain our position at the forefront of the hospitality industry now and in the future.\nOur hotels are service driven, responsible, socially and locally connected and demonstrate a modern friendly attitude in everything we do. Our aim is to deliver our outstanding `Yes I Can!` ^SM^ service, comfort and style where you need us.\n\n**THE RADISSON^®^ WAY** Always positive, always smiling and always professional, Radisson people set Radisson apart. Every member of the team has a dedication to `Yes I Can!` ^SM^ hospitality – a passion for ensuring the total wellbeing and satisfaction of each individual guest. Imaginative, understanding and truly empathetic to the needs of the modern traveler, they are people on a special mission to deliver exceptional Extra Thoughtful Care.', + 'no_of_rooms': '35', 'opened_on': { date_value: '2013-06-11', time_value: '00:00:00' }, + 'date_col': '2008-12-09', 'luxurious': 'false', 'json_col': JSON.stringify({ 'age': 25, 'name': 'Testing' }, undefined, 2), + 'text_array': ['val2'], 'boolean_array': ['false'], 'int4_array': ['1'], 'float4_array': ['1.1', '2.2'], + 'date_array': '', 'timestamp_array': '', + 'timestamptz_array': [{ date_value: '2022-02-02', time_value: '02:02:02' }, { date_value: '2024-04-04', time_value: '04:04:04' }], + 'color_rgb_hex_column': '#423456' + } + ], + inputs: [ + { + 'title': 'Very simple Resort', 'website': 'http://simple-resort.com', 'category': { modal_num_rows: 5, modal_option_index: 3, rowName: 'Resort' }, + 'rating': '1.2', 'summary': 'A very simple resort', 'description': '_A resort!_', + 'no_of_rooms': '100', 'opened_on': { date_value: '2020-01-01', time_value: '01:01:01' }, 'date_col': '2020-12-01', 'luxurious': 'true', + 'boolean_array': ['true'], 'int4_array': ['1'], 'float4_array': ['-0.5'], + 'date_array': ['2002-02-02', '2002-02-02'], 'timestamp_array': [{ date_value: '2015-03-04', time_value: '12:12:12' }], + 'timestamptz_array': [{ date_value: '2005-05-05', time_value: '10:01:01' }, { date_value: '2004-04-04', time_value: '11:11:11' }] + }, + { + 'category': { modal_num_rows: 5, modal_option_index: 4, rowName: 'Castle' }, 'website': 'http://best-castle.com', + 'rating': '3.4', 'opened_on': { date_value: '2015-05-05', time_value: '05:05:05' }, 'date_col': '2018-12-01', + 'summary': 'Best castle in the world!', 'description': '**Best castle in the world!**', + 'float4_array': ['5'], + 'color_rgb_hex_column': '#999999' + } + ] + }, + submission: { + tableDisplayname: 'Accommodations', + resultColumnNames: [ + 'Name of Accommodation', 'Website', 'Category', 'User Rating', 'Summary', 'Description', 'Number of Rooms', + 'Operational Since', 'date_col', 'Is Luxurious', + 'text_array', 'boolean_array', 'int4_array', 'float4_array', 'date_array', 'timestamp_array', 'timestamptz_array', 'color_rgb_hex_column' + ], + resultRowValues: [ + [ + 'Very simple Resort', { url: 'http://simple-resort.com', caption: 'Link to Website' }, { url: '/product-edit:category/', caption: 'Resort' }, + '1.2000', 'A very simple resort', 'A resort!', '100', '2020-01-01 01:01:01', '2020-12-01', 'true', + 'val1', 'true', '1', '-0.5000', '2002-02-02, 2002-02-02', '2015-03-04 12:12:12', + '2005-05-05 10:01:01, 2004-04-04 11:11:11', '#223456' + ], + [ + 'Super 8 North Hollywood Motel', { url: 'http://best-castle.com', caption: 'Link to Website' }, { url: '/product-edit:category/', caption: 'Castle' }, + '3.4000', 'Best castle in the world!', 'Best castle in the world!', '35', '2015-05-05 05:05:05', '2018-12-01', 'false', + 'val2', 'false, false, true', '1, 2, 3', '5.0000', '', '', '2022-02-02 02:02:02, 2024-04-04 05:04:04', '#999999' + ] + ] + } + }, + { + num_files: 1, + filter: 'id=90008', + presentation: { + description: 'single edit file upload', + ...fileTableDefaultPresentationProps, + rowNames: ['90008'], + values: [ + { 'fileid': '', 'uri': 'Four Points Sherathon 3', 'timestamp_txt': '' } + ], + inputs: [ + { 'fileid': '4', 'uri': testFiles[0], 'timestamp_txt': currentTimestampTimeStr } + ] + }, + submission: { + tableDisplayname: 'file', + resultColumnNames: ['fileid', 'uri', 'filename', 'bytes'], + resultRowValues: [ + [ + '4', + { caption: 'testfile500kb_edit.png', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/4/.png/` }, + 'testfile500kb_edit.png', + '512 kB' + ] + ] + } + }, + { + num_files: 2, + filter: 'id=any(3005,3006)@sort(id)', + presentation: { + description: 'multi edit file upload', + ...fileTableDefaultPresentationProps, + rowNames: ['3005', '3006'], + values: [ + { 'fileid': '', 'uri': 'Four Points Sherathon 1', 'timestamp_txt': '' }, + { 'fileid': '', 'uri': 'Four Points Sherathon 2', 'timestamp_txt': '' } + ], + inputs: [ + { 'fileid': '5', 'uri': testFiles[0], 'timestamp_txt': currentTimestampTimeStr }, + { 'fileid': '6', 'uri': testFiles[1], 'timestamp_txt': currentTimestampTimeStr } + ] + }, + submission: { + tableDisplayname: 'file', + resultColumnNames: ['fileid', 'uri', 'filename', 'bytes'], + resultRowValues: [ + [ + '5', + { caption: 'testfile500kb_edit.png', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/5/.png/` }, + 'testfile500kb_edit.png', + '512 kB' + ], + [ + '6', + { caption: 'testfile1MB_edit.txt', url: `/hatrac/js/chaise/${currentTimestampTimeStr}/6/.txt/` }, + 'testfile1MB_edit.txt', + '1.02 MB' + ] + ] + } + } + ] +} + + +test.describe('Recordedit edit', () => { + test.describe.configure({ mode: 'parallel' }); + + for (const [index, params] of testParams.tables.entries()) { + const presentation = params.presentation; + + test(`${presentation.description}`, async ({ page, baseURL }, testInfo) => { + + // create files if this is the first one + if (index === 0) { + await test.step('create files', async () => { + await createFiles(testFiles); + }); + } + + await test.step('open recordedit page', async () => { + const url = `${baseURL}/recordedit/#${getCatalogID(testInfo.project.name)}/${presentation.schemaName}:${presentation.tableName}`; + await page.goto(url + '/' + params.filter); + }); + + // test everything related to the form + await testFormPresentationAndValidation(page, baseURL, testInfo, presentation, true); + + await test.step('submit and save the data', async () => { + let timeout: number | undefined; + if (params.num_files > 0) { + timeout = params.num_files * 30 * 1000; + } + await testSubmission(page, params.submission, true, timeout); + }); + + + // remove files if this is the last one + if (index === testParams.tables.length - 1) { + await test.step('delete create files', async () => { + await deleteFiles(testFiles); + await deleteHatracNamespaces([`/hatrac/js/chaise/${currentTimestampTimeStr}`]); + }); + } + }); + } + + test('remove form button', async ({ page, baseURL }, testInfo) => { + await test.step('open recordedit page', async () => { + const url = `${baseURL}/recordedit/#${getCatalogID(testInfo.project.name)}/product-edit:booking`; + await page.goto(url + '/id=any(1,2,3,4,5,6,7,8,9,10,11,12)@sort(id)'); + }); + + await test.step('remove several forms and edit some of the data"', async () => { + const formsToBeRemoved = [8, 6, 3, 2, 0]; + const originalNumRows = 12; + + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(originalNumRows); + + for await (const [index, formIndex] of formsToBeRemoved.entries()) { + await RecordeditLocators.getDeleteRowButton(page, formIndex).click(); + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(originalNumRows - (index+1)); + } + + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(originalNumRows - formsToBeRemoved.length); + + // the first form (formNumber=1) is not visible anymore + const inp = RecordeditLocators.getInputForAColumn(page, 'price', 2); + await inp.clear(); + await inp.fill('2500'); + }); + + await test.step('submit and save data.', async () => { + await testSubmission(page, { + tableDisplayname: 'booking', + resultColumnNames: ['id', 'accommodation_id', 'price', 'booking_date'], + resultRowValues: [ + ['2', '2002', '2,500.0000', '2016-04-18 00:00:00'], + ['5', '2003', '240.0000', '2016-01-25 00:00:00'], + ['6', '2003', '320.0000', '2016-02-09 00:00:00'], + ['8', '2004', '125.0000', '2016-03-12 00:00:00'], + ['10', '2004', '110.0000', '2016-05-19 01:00:00'], + ['11', '2004', '120.0000', '2015-11-10 00:00:00'], + ['12', '2004', '180.0000', '2016-09-04 01:00:00'], + ] + }, true); + }); + + }); +}); diff --git a/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts b/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts index de122cbf3..2b8ff81e2 100644 --- a/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts +++ b/test/e2e/specs/default-config/multi-form-input/multi-form-input-create.spec.ts @@ -456,6 +456,7 @@ const testParams = { }, { type: RecordeditInputType.ARRAY, + arrayBaseType: RecordeditInputType.TEXT, column_name: 'array_text', column_displayname: 'array_text', apply_to_all: { @@ -672,7 +673,7 @@ test.describe('multi form input in create mode', () => { const colDisplayname = params.column_displayname; const toggleBtn = RecordeditLocators.getMultiFormToggleButton(page, colDisplayname); const applybtn = RecordeditLocators.getMultiFormApplyBtn(page); - const clearBtn = RecordeditLocators.getMultiFormClearBtn(page) + const clearBtn = RecordeditLocators.getMultiFormClearBtn(page); await test.step(`${params.type}`, async () => { @@ -696,7 +697,9 @@ test.describe('multi form input in create mode', () => { }); await test.step('when all forms are selected, clicking on apply should apply change to all forms', async () => { - await setInputValue(page, MULI_FORM_INPUT_FORM_NUMBER, params.column_name, colDisplayname, params.type, params.apply_to_all.value); + await setInputValue( + page, MULI_FORM_INPUT_FORM_NUMBER, params.column_name, colDisplayname, params.type, params.apply_to_all.value, params.arrayBaseType + ); await applybtn.click(); await testFormValuesForAColumn(page, params.column_name, colDisplayname, params.type, true, params.apply_to_all.column_values_after); }); @@ -707,7 +710,9 @@ test.describe('multi form input in create mode', () => { await RecordeditLocators.getFormInputCell(page, params.column_name, f, params.type === RecordeditInputType.ARRAY).click(); } - await setInputValue(page, MULI_FORM_INPUT_FORM_NUMBER, params.column_name, colDisplayname, params.type, params.apply_to_some.value); + await setInputValue( + page, MULI_FORM_INPUT_FORM_NUMBER, params.column_name, colDisplayname, params.type, params.apply_to_some.value, params.arrayBaseType + ); await applybtn.click(); await testFormValuesForAColumn(page, params.column_name, colDisplayname, params.type, true, params.apply_to_some.column_values_after); }); @@ -728,7 +733,9 @@ test.describe('multi form input in create mode', () => { await expect.soft(applybtn).not.toBeAttached(); // change one value manually - await setInputValue(page, params.manual_test.formNumber, params.column_name, colDisplayname, params.type, params.manual_test.value); + await setInputValue( + page, params.manual_test.formNumber, params.column_name, colDisplayname, params.type, params.manual_test.value, params.arrayBaseType + ); // make sure the value shows up properly in the form. await testFormValuesForAColumn(page, params.column_name, colDisplayname, params.type, false, params.manual_test.column_values_after); }); diff --git a/test/e2e/specs/default-config/multi-form-input/multi-form-input-edit.spec.ts b/test/e2e/specs/default-config/multi-form-input/multi-form-input-edit.spec.ts index 1d8c30416..4d46ed7d7 100644 --- a/test/e2e/specs/default-config/multi-form-input/multi-form-input-edit.spec.ts +++ b/test/e2e/specs/default-config/multi-form-input/multi-form-input-edit.spec.ts @@ -82,6 +82,15 @@ test.describe('Regarding multi form input button', () => { await RecordeditLocators.getMultiFormApplyBtn(page).click(); }); + // we used to have bug where the clear all wasn't working as expected for fkeys. so we're specifically testing it here + await test.step('user should be able to clear all foreign key values.', async () => { + const clearBtn = RecordeditLocators.getMultiFormClearBtn(page); + await RecordeditLocators.getMultiFormToggleButton(page, 'fk_col').click(); + await RecordeditLocators.getMultiFormInputCheckbox(page).click(); + await expect.soft(clearBtn).not.toBeDisabled(); + await clearBtn.click(); + }); + await test.step('user should be able to submit and save data.', async () => { /** * increse the timeout because of upload modal diff --git a/test/e2e/specs/default-config/recordedit/multi-col-types.conf.js b/test/e2e/specs/default-config/recordedit/multi-col-types.conf.js deleted file mode 100644 index f23811967..000000000 --- a/test/e2e/specs/default-config/recordedit/multi-col-types.conf.js +++ /dev/null @@ -1,15 +0,0 @@ -var pConfig = require('./../../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - configFileName: 'recordedit/multi-column-types.dev.json', - chaiseConfigFilePath: 'test/e2e/specs/default-config/chaise-config.js', - specs: [ - "multi-col-types.spec.js" - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - } -}); - -exports.config = config; diff --git a/test/e2e/specs/default-config/recordedit/multi-col-types.spec.js b/test/e2e/specs/default-config/recordedit/multi-col-types.spec.js deleted file mode 100644 index ed726b074..000000000 --- a/test/e2e/specs/default-config/recordedit/multi-col-types.spec.js +++ /dev/null @@ -1,360 +0,0 @@ -// The goal of this spec is to test whether RecordEdit app correctly converts data for different column types for submission to ERMrest by verifying the contents that we expect to show up on record page, show up properly. -var moment = require('moment'); - -var recordEditHelpers = require('../../../utils/recordedit-helpers.js'); -var chaisePage = require('../../../utils/chaise.page.js'); -var recordEditPage = chaisePage.recordEditPage; -var currentTimestampTime = moment().format("x"); - -var files = [{ - name: "testfile500kb_nulltest.png", - size: "512000", - displaySize: "500KB", - path: "testfile500kb_nulltest.png", -}]; -var testParams = { - table_1: { - tableName: "table_1", - key: {columnName: "id", value: 1, operator: "="}, - row: [ - {name: "int2_null_col", displayType: "int2", value: 32767}, - {name: "int2_col", displayType: "int2"}, - {name: "int4_null_col", displayType: "int4", value: -2147483648}, - {name: "int4_col", displayType: "int4"}, - {name: "int8_null_col", displayType: "int8", value: 9007199254740991}, - {name: "int8_col", displayType: "int8"}, - {name: "float4_null_col", displayType: "float4", value: 4.6123}, - {name: "float4_col", displayType: "float4"}, - {name: "float8_null_col", displayType: "float8", value: 234523523.023045}, - {name: "float8_col", displayType: "float8"}, - {name: "text_null_col", displayType: "text", value: "sample"}, - {name: "text_col", displayType: "text"}, - {name: "longtext_null_col", displayType: "longtext", value: "asjdf;laksjdf;laj ;lkajsd;f lkajsdf;lakjs f;lakjs df;lasjd f;ladsjf;alskdjfa ;lskdjf a;lsdkjf a;lskdfjal;sdkfj as;ldfkj as;dlf kjasl;fkaj;lfkjasl;fjas;ldfkjals;dfkjas;dlkfja;sldkfjasl;dkfjas;dlfkjasl;dfkja; lsdjfk a;lskdjf a;lsdfj as;ldfja;sldkfja;lskjdfa;lskdjfa;lsdkfja;sldkfjas;ldfkjas;dlfkjas;lfkja;sldkjf a;lsjf ;laskj fa;slk jfa;sld fjas;l js;lfkajs;lfkasjf;alsja;lk ;l kja"}, - {name: "longtext_col", displayType: "longtext"}, - {name: "markdown_null_col", displayType: "markdown", value: "Sample"}, - {name: "markdown_col", displayType: "markdown"}, - {name: "bool_null_col", displayType: "boolean", value: true}, - {name: "bool_true_col", displayType: "boolean", value: false}, - {name: "bool_false_col", displayType: "boolean", value: null}, - {name: "timestamp_null_col", displayType: "timestamp", value: {date: "2016-01-18", time: "13:00:00"}}, - {name: "timestamp_col", displayType: "timestamp"}, - {name: "timestamptz_null_col", displayType: "timestamptz", value: {date: "2016-01-18", time: "13:00:00"}}, - {name: "timestamptz_col", displayType: "timestamptz"}, - {name: "date_null_col", displayType: "date", value: "2016-08-15"}, - {name: "date_col", displayType: "date"}, - {name: "fk_null_col", displayType: "popup-select"}, - {name: "fk_col", displayType: "popup-select"}, - {name: "json_null_col", displayType: "json", value: "89.586"}, - {name: "json_col", displayType: "json"}, - {name: "timestamp_txt", displayType: "text", value: currentTimestampTime}, // used for generating the hatrac path - {name: "asset_null_col", displayType: "asset", value: files[0]}, - {name: "asset_col", displayType: "asset"}, - {name: "color_rgb_hex_null_col", displayType: "color", value: "#123456"}, - {name: "color_rgb_hex_col", displayType: "color"}, - ], - submitted_values: { - int2_col: "32,767", - int4_col: "-2,147,483,648", - int8_col: "9,007,199,254,740,991", - float4_col: "4.6123", - float8_col: "234,523,523.0230", - text_col: "sample", - longtext_col: "asjdf;laksjdf;laj ;lkajsd;f lkajsdf;lakjs f;lakjs df;lasjd f;ladsjf;alskdjfa ;lskdjf a;lsdkjf a;lskdfjal;sdkfj as;ldfkj as;dlf kjasl;fkaj;lfkjasl;fjas;ldfkjals;dfkjas;dlkfja;sldkfjasl;dkfjas;dlfkjasl;dfkja; lsdjfk a;lskdjf a;lsdfj as;ldfja;sldkfja;lskjdfa;lskdjfa;lsdkfja;sldkfjas;ldfkjas;dlfkjas;lfkja;sldkjf a;lsjf ;laskj fa;slk jfa;sld fjas;l js;lfkajs;lfkasjf;alsja;lk ;l kja", - markdown_col: "Sample", - bool_true_col: "true", - bool_false_col: "false", - timestamp_col: "2016-01-18 13:00:00", - timestamptz_col: "2016-01-18 00:00:00", - date_col: "2016-08-15", - // Value of foreign (fk_col) related entity - "PhF3HG1fOZs72s_xbuLx4Q": "Abraham Lincoln", - json_null_col: "null", - json_col: '"89.586"', - asset_col: {link: "/hatrac/js/chaise/somepath.png", value: "filenamevalue.png"}, - asset_col_filename: "filenamevalue.png", - asset_col_bytes: "12.3 kB", - asset_col_md5: "md5value", - color_rgb_hex_col: "#123456" - }, - // the rest of the columns are null and therefore not displayed: - null_submitted_values: { - int2_null_col: "32,767", - int4_null_col: "-2,147,483,648", - int8_null_col: "9,007,199,254,740,991", - float4_null_col: "4.6123", - float8_null_col: "234,523,523.0230", - text_null_col: "sample", - longtext_null_col: "asjdf;laksjdf;laj ;lkajsd;f lkajsdf;lakjs f;lakjs df;lasjd f;ladsjf;alskdjfa ;lskdjf a;lsdkjf a;lskdfjal;sdkfj as;ldfkj as;dlf kjasl;fkaj;lfkjasl;fjas;ldfkjals;dfkjas;dlkfja;sldkfjasl;dkfjas;dlfkjasl;dfkja; lsdjfk a;lskdjf a;lsdfj as;ldfja;sldkfja;lskjdfa;lskdjfa;lsdkfja;sldkfjas;ldfkjas;dlfkjas;lfkja;sldkjf a;lsjf ;laskj fa;slk jfa;sld fjas;l js;lfkajs;lfkasjf;alsja;lk ;l kja", - markdown_null_col: "Sample", - bool_null_col: "true", - bool_true_col: "false", - timestamp_null_col: "2016-01-18 13:00:00", - timestamptz_null_col: "2016-01-18 13:00:00", - date_null_col: "2016-08-15", - // Value of foreign (fk_null_col) related entity - "Un6B-zCfMiIKZGKbF1TPFw": "Abraham Lincoln", - json_null_col: "89.586", - timestamp_txt: currentTimestampTime, - asset_null_col: {ignoreInCI: true, link: "/hatrac/js/chaise/" + currentTimestampTime + "/multi-col-asset-null/", value: "testfile500kb_nulltest.png"}, - asset_null_col_bytes: {ignoreInCI: true, value: "512 kB"}, - color_rgb_hex_null_col: "#123456" - } - }, - table_w_generated_columns : { - tableName: "table_w_generated_columns", - key: {columnName: "id", value: 1, operator: "="}, - row: [ - {name: "int2_col_gen", value: "32767", displayType: "disabled"}, - {name: "int4_col_gen", value: "-2147483648", displayType: "disabled"}, - {name: "int8_col_gen", value: "9007199254740991", displayType: "disabled"}, - {name: "float4_col_gen", value: "4.6123", displayType: "disabled"}, - {name: "float8_col_gen", value: "234523523.023045", displayType: "disabled"}, - {name: "text_col_gen", value: "sample", displayType: "disabled"}, - {name: "longtext_col_gen", value: "asjdf;laksjdf;laj ;lkajsd;f lkajsdf;lakjs f;lakjs df;lasjd f;ladsjf;alskdjfa ;lskdjf a;lsdkjf a;lskdfjal;sdkfj as;ldfkj as;dlf kjasl;fkaj;lfkjasl;fjas;ldfkjals;dfkjas;dlkfja;sldkfjasl;dkfjas;dlfkjasl;dfkja; lsdjfk a;lskdjf a;lsdfj as;ldfja;sldkfja;lskjdfa;lskdjfa;lsdkfja;sldkfjas;ldfkjas;dlfkjas;lfkja;sldkjf a;lsjf ;laskj fa;slk jfa;sld fjas;l js;lfkajs;lfkasjf;alsja;lk ;l kja", displayType: "textarea"}, - {name: "markdown_col_gen", value: "Sample", displayType: "textarea"}, - {name: "bool_true_col_gen", value: "true", displayType: "boolean"}, - {name: "bool_false_col_gen", value: "false", displayType: "boolean"}, - {name: "timestamp_col_gen", value: { date: "2016-01-18", time: "13:00:00" }, displayType: "timestamp"}, - {name: "timestamptz_col_gen", value: { date: "2016-01-18", time: "00:00:00" }, displayType: "timestamp"}, - {name: "date_col_gen", value: "2016-08-15", displayType: "disabled"}, - {name: "fk_col_gen", value: "Abraham Lincoln", displayType: "fk"}, - {name: "asset_col_gen", value: "test", displayType: "upload"} - ] - } -}; - -// When editing a record, the app should reliably submit the right data to ERMrest -describe('When editing a record', function() { - - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/multi-column-types:" + testParams.table_w_generated_columns.tableName + '/' + testParams.table_w_generated_columns.key.columnName + testParams.table_w_generated_columns.key.operator + testParams.table_w_generated_columns.key.value); - chaisePage.recordeditPageReady(); - - if (!process.env.CI && files.length > 0) { - // create files that will be uploaded - recordEditHelpers.createFiles(files); - console.log("\n"); - } - }); - - // Tests that check the values for regular, non-disabled input fields are in 01-recordedit.edit.spec.js - it('should display the correct values in disabled input fields', function(done) { - testParams.table_w_generated_columns.row.forEach(function checkInput(col) { - let input, inputControl; - // Upload input is disabled, but not the same displayType (input field) as other disabled inputs - switch (col.displayType) { - case 'upload': - inputControl = recordEditPage.getInputControlForAColumn(col.name, 1); - input = recordEditPage.getTextFileInputForAColumn(col.name, 1); - expect(inputControl.getAttribute('class')).toContain('input-disabled', "col " + col.name + " was not disabled."); - expect(input.getText()).toBe(col.value, "col " + col.name + " value missmatch."); - break; - case 'textarea': - input = recordEditPage.getTextAreaForAColumn(col.name, 1); - expect(input.isEnabled()).toBeFalsy("col " + col.name + " was not disabled."); - expect(input.getAttribute('value')).toBe(col.value, "col " + col.name + " value missmatch."); - break; - case 'boolean': - inputControl = recordEditPage.getInputControlForAColumn(col.name, 1); - input = recordEditPage.getDropdownElementByName(col.name, 1); - expect(inputControl.getAttribute('class')).toContain('input-disabled', "col " + col.name + " was not disabled."); - expect(input.getText()).toBe(col.value, "col " + col.name + " value missmatch."); - break; - case 'timestamp': - input = recordEditPage.getTimestampInputsForAColumn(col.name, 1); - expect(input.date.isEnabled()).toBeFalsy("col " + col.name + " date was not disabled."); - expect(input.time.isEnabled()).toBeFalsy("col " + col.name + " time was not disabled."); - - expect(input.date.getAttribute('value')).toBe(col.value.date, "col " + col.name + " date value missmatch."); - expect(input.time.getAttribute('value')).toBe(col.value.time, "col " + col.name + " time value missmatch."); - break; - case 'fk': - input = recordEditPage.getForeignKeyInputDisplay(col.name, 1); - expect(input.getAttribute('class')).toContain('input-disabled', "col " + col.name + " was not disabled."); - expect(input.getText()).toBe(col.value, "col " + col.name + " value missmatch."); - break; - default: - input = recordEditPage.getInputForAColumn(col.name, 1); - expect(input.isEnabled()).toBeFalsy("col " + col.name + " was not disabled."); - expect(input.getAttribute('value')).toBe(col.value, "col " + col.name + " value missmatch."); - break; - } - - done(); - }); - }); - - // TODO this was causing issues in CI because of timezone - // we're also already testing this scenario in submission-disabled.spec so it's not needed. - // describe('if the user made no edits', function() { - // beforeAll(function(done) { - // chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/multi-column-types:" + testParams.table_1.tableName + '/' + testParams.table_1.key.columnName + testParams.table_1.key.operator + testParams.table_1.key.value); - // chaisePage.recordeditPageReady().then(function() { - // return recordEditPage.submitForm(); - // }).then(() => { - // done(); - // }).catch(chaisePage.catchTestError(done)); - // }); - - // it ('should show a warning letting users know that they need to make a change.', (done) => { - // const alert = chaisePage.recordEditPage.getAlertWarning(); - // chaisePage.waitForElement(alert).then(() => { - // expect(alert.getText()).toContain("No data was changed in the update request. Please check the form content and resubmit the data."); - // done(); - // }).catch(chaisePage.catchTestError(done)); - // }) - - // TODO we're not making any edits, so why this test case expects recordedit to submit? - // it('should submit the right data to the DB', function() { - // var redirectUrl = browser.params.url + "/record/#" + browser.params.catalogId + "/multi-column-types:" + testParams.table_1.tableName + '/'; - - // browser.wait(function () { - // return browser.driver.getCurrentUrl().then(function(url) { - // return url.startsWith(redirectUrl); - // }); - // }); - - // expect(browser.driver.getCurrentUrl()).toContain(redirectUrl); - // var colNames = Object.keys(testParams.table_1.submitted_values); - // recordEditHelpers.testRecordAppValuesAfterSubmission(colNames, testParams.table_1.submitted_values, colNames.length+5); // +5 for system columns - // }); - // }); - - // If the user does make an edit, make sure the app correctly converted the submission data for ERMrest. - // We test this conversion on a per-column-type basis, with 2 test cases for each type: - // 1. Converting from null to non-null (e.g. "int2_null_col" will be changed from null to 32767) - // 2. Converting from non-null to null (e.g. "int2_col" will be changed from 32767 to null) - // Except boolean type gets 3 cases (null to true, true to false, false to null). - describe('if the user did make edits', function() { - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/multi-column-types:" + testParams.table_1.tableName + '/' + testParams.table_1.key.columnName + testParams.table_1.key.operator + testParams.table_1.key.value); - chaisePage.recordeditPageReady(); - }); - - // Test each column type to check that the app converts the submission data correctly for each type - it('should properly set the values.', function() { - // Edit each column with the new row data - testParams.table_1.row.forEach(function(column) { - var newValue = column.value; - var name = column.name; - switch (column.displayType) { - case 'popup-select': - // Clear the foreign key field for fk_col b/c fk_col needs to be null - if (name === 'fk_col') { - chaisePage.clickButton(chaisePage.recordEditPage.getForeignKeyInputClear(name, 1)); - } - // Select a non-null value for fk_null_col b/c fk_null_col needs to be non-null - if (name === 'fk_null_col') { - element.all(by.css('.modal-popup-btn')).first().click().then(function() { - // wait for modal rows to load - return browser.wait(function () { - return chaisePage.recordsetPage.getModalRows().count().then(function (ct) { - return (ct > 0); - }); - }, browser.params.defaultTimeout); - }).then(function() { - // Get the first row in the modal popup table, find the row's select-action-buttons, and click the 1st one. - return chaisePage.recordsetPage.getModalRows().get(0).all(by.css(".select-action-button")); - }).then(function (selectButtons) { - selectButtons[0].click(); - }).catch(function(error) { - console.log(error); - expect('Something went wrong in this promise chain.').toBe('Please see error message.'); - }); - } - break; - case 'timestamp': - case 'timestamptz': - var inputs = recordEditPage.getTimestampInputsForAColumn(name, 1); - var dateInput = inputs.date, timeInput = inputs.time; - chaisePage.clickButton(inputs.clearBtn).then(function() { - if (newValue) { - dateInput.sendKeys(newValue.date); - timeInput.sendKeys(newValue.time); - } - }).catch(function(error) { - console.log(error); - expect('Something went wrong in this promise chain.').toBe('Please see error message.'); - }); - break; - case 'boolean': - if (newValue !== null) { - recordEditPage.selectDropdownValue(recordEditPage.getDropdownElementByName(name, 1), newValue); - } else { - chaisePage.clickButton(chaisePage.recordEditPage.getInputRemoveButton(name, 1)); - } - break; - case "asset": - // empty the value if there's any - if (name === 'asset_col') { - chaisePage.clickButton(chaisePage.recordEditPage.getInputRemoveButton(name, 1)); - } - // select new file - if (newValue && !process.env.CI) { - recordEditHelpers.testFileInput(name, 0, newValue, "", true, false); - } - break; - case 'color': - var input = chaisePage.recordEditPage.getColorInputForAColumn(name, 1); - // we want to empty the value - if (name === 'color_rgb_hex_col') { - chaisePage.clickButton(chaisePage.recordEditPage.getInputRemoveButton(name, 1)); - } - // select new value - if (newValue) { - input.sendKeys(newValue); - } - break; - case 'longtext': - case 'markdown': - case 'json': - var input = recordEditPage.getTextAreaForAColumn(name, 1); - chaisePage.recordEditPage.clearInput(input); - input.clear().then(function() { - if (newValue) input.sendKeys(newValue); - }).catch(function(error) { - console.log(error); - expect('Something went wrong in this promise chain.').toBe('Please see error message.'); - }); - break; - default: - var input = recordEditPage.getInputForAColumn(name, 1); - chaisePage.recordEditPage.clearInput(input); - input.clear().then(function() { - if (newValue) input.sendKeys(newValue); - }).catch(function(error) { - console.log(error); - expect('Something went wrong in this promise chain.').toBe('Please see error message.'); - }); - } // match to switch statement - }); - }); - - it ('should submit the data properly and redirect to record page.', () => { - // Submit the form - recordEditPage.submitForm().then(function() { - var redirectUrl = browser.params.url + "/record/#" + browser.params.catalogId + "/multi-column-types:" + testParams.table_1.tableName + '/'; - browser.wait(function () { - return browser.driver.getCurrentUrl().then(function(url) { - return url.startsWith(redirectUrl); - }); - }); - }); - }); - - it ('data should be properly saved.', () => { - var colNames = Object.keys(testParams.table_1.null_submitted_values).filter(function (colName) { - var el = testParams.table_1.null_submitted_values[colName]; - return !process.env.CI || !(typeof el === 'object' && el != null && el.ignoreInCI === true); - }); - recordEditHelpers.testRecordAppValuesAfterSubmission(colNames, testParams.table_1.null_submitted_values, colNames.length+5); // +5 for system columns - }); - }); - - if (!process.env.CI && files.length > 0) { - afterAll(function() { - recordEditHelpers.deleteFiles(files); - console.log("\n"); - }); - } -}); diff --git a/test/e2e/specs/default-config/recordedit/multi-edit.conf.js b/test/e2e/specs/default-config/recordedit/multi-edit.conf.js deleted file mode 100644 index 76eacfdb3..000000000 --- a/test/e2e/specs/default-config/recordedit/multi-edit.conf.js +++ /dev/null @@ -1,16 +0,0 @@ -var pConfig = require('./../../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - configFileName: 'recordedit/edit-multi.dev.json', - chaiseConfigFilePath: 'test/e2e/specs/default-config/chaise-config.js', - specs: [ - "multi-edit.spec.js", - "remove-edit-form.spec.js" - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - } -}); - -exports.config = config; diff --git a/test/e2e/specs/default-config/recordedit/multi-edit.spec.js b/test/e2e/specs/default-config/recordedit/multi-edit.spec.js deleted file mode 100644 index a78961cc3..000000000 --- a/test/e2e/specs/default-config/recordedit/multi-edit.spec.js +++ /dev/null @@ -1,354 +0,0 @@ -var testConfiguration = browser.params.configuration; -var chaisePage = require('../../../utils/chaise.page.js'); -var recordEditHelpers = require('../../../utils/recordedit-helpers.js'); -var moment = require('moment'); -var EC = protractor.ExpectedConditions - -var currentTimestampTime = moment().format("x"); -var testParams = { - schema_name: "multi-edit", - tables: [ - { - table_name: "multi-edit-table", - tableComment: "Table to represent adding multiple entities", - sortColumns: "id", - testFkClear: "true", - fkColumnName: "fk_to_f1", - keys: [ - {name: "id", value: "1000", operator: "="}, - {name: "id", value: "1001", operator: "="} - ], - rows: [{ - "int": {"value": "7", "input": "4"}, - "text": {"value": "test text", "input": "modified val"}, - "json_col":{isTextArea: true, "value":JSON.stringify({"name":"testing json column"},undefined,2),"input" : "{\"name\":\"This is the edited value of json\"}"}, - "jsonb_col":{isTextArea: true, "value":JSON.stringify({"name":"testing jsonB column"},undefined,2),"input" : "{\"name\":\"This is the edited value of jsonB\"}"}, - }, { - "int": {"value": "12", "input": "66"}, - "text": {"value": "description", "input": "description 2"}, - "json_col":{isTextArea: true, "value":JSON.stringify({"quantity":"6"},undefined,2),"input" : "{\"quantity\":\"6\"}"}, - "jsonb_col":{isTextArea: true, "value":JSON.stringify({"quantity":"9"},undefined,2), "input" : "{\"quantity\":\"9\"}"} - } - ], - results: [ - ["1000", "modified val", "4", JSON.stringify({"name":"This is the edited value of json"},undefined,2), JSON.stringify({"name":"This is the edited value of jsonB"},undefined,2), "1"], - ["1001", "description 2", "66", JSON.stringify({"quantity":"6"},undefined,2), JSON.stringify({"quantity":"9"},undefined,2), "2"] - ] - }, - { - table_name: "multi-edit-table", - tableComment: "Table to represent adding multiple entities", - sortColumns: "id", - keys: [ - {name: "id", value: "1000", operator: "="}, - {name: "id", value: "1001", operator: "="}, - {name: "id", value: "1002", operator: "="} - ], - rows: [{ - "int": {"value": "4", "input": "5"}, - "text": {"value": "modified val", "input": "changed it again"} - }, { - "int": {"value": "66", "input": "768"}, - "text": {"value": "description 2", "input": "description 3"} - }, - { - "int": {"value": "34", "input": "934"}, - "text": {"value": "just text", "input": "I am number 3"} - } - ], - // apart from the values changed above, the fk values are also empty because the first test case is also doing testFkClear - results: [ - ["1000", "changed it again", "5", JSON.stringify({"name":"This is the edited value of json"},undefined,2), JSON.stringify({"name":"This is the edited value of jsonB"},undefined,2), ""], - ["1001", "description 3", "768", JSON.stringify({"quantity":"6"},undefined,2), JSON.stringify({"quantity":"9"},undefined,2), ""], - ["1002", "I am number 3", "934", JSON.stringify(979.998,undefined,2), JSON.stringify(98.00243,undefined,2), ""] - ] - }, - { - table_name: 'table_w_multiple_assets', - tableComment: "table that has three file assets", - sortColumns: "id", - keys: [ - {name: "id",value: "1", operator: "="}, - {name: "id",value: "2",operator: "="} - ], - rows: [{ - "file_1": {"isFile": true, "value": "/hatrac/js/chaise/prev/111111", "input": 0}, - "file_2": {"isFile": true, "value": "/hatrac/js/chaise/prev/222222", "input": 1}, - "file_3": {"isFile": true, "value": "/hatrac/js/chaise/prev/333333", "input": 2}, - "timestamp_txt": {"input": currentTimestampTime, "value": ""} - }, - { - "file_1": {"isFile": true, "value": "/hatrac/js/chaise/prev/111111", "input": 0}, - "file_2": {"isFile": true, "value": "/hatrac/js/chaise/prev/222222", "input": 1}, - "file_3": {"isFile": true, "value": "/hatrac/js/chaise/prev/333333", "input": 2}, - "timestamp_txt": {"input": currentTimestampTime, "value": ""} - } - ], - results: [ - [ - {"link": "/hatrac/js/chaise/"+currentTimestampTime+"/value/","value": "testfile500kb_1.png"}, "testfile500kb_1.png", "512 kB", - {"link": "/hatrac/js/chaise/"+currentTimestampTime+"/generated/","value": "testfile500kb_2.png"}, "testfile500kb_2.png", "512 kB", - {"link": "/hatrac/js/chaise/"+currentTimestampTime+"/generated_inv/","value": "testfile500kb_3.png"}, "testfile500kb_3.png", "512 kB" - ], - [ - {"link": "/hatrac/js/chaise/"+currentTimestampTime+"/value/","value": "testfile500kb_1.png"}, "testfile500kb_1.png", "512 kB", - {"link": "/hatrac/js/chaise/"+currentTimestampTime+"/generated/","value": "testfile500kb_2.png"}, "testfile500kb_2.png", "512 kB", - {"link": "/hatrac/js/chaise/"+currentTimestampTime+"/generated_inv/","value": "testfile500kb_3.png"}, "testfile500kb_3.png", "512 kB" - ] - ], - files: [{ - name: "testfile500kb_1.png", - size: "512000", - displaySize: "500KB", - path: "testfile500kb_1.png", - tooltip: "- testfile500kb_1.png\n- 500 kB" - }, - { - name: "testfile500kb_2.png", - size: "512000", - displaySize: "500KB", - path: "testfile500kb_2.png", - tooltip: "- testfile500kb_2.png\n- 500 kB" - }, - { - name: "testfile500kb_3.png", - size: "512000", - displaySize: "500KB", - path: "testfile500kb_3.png", - tooltip: "- testfile500kb_3.png\n- 500 kB" - } - ] - } - ] - -}; - -if (!process.env.CI) { - // keep track of namespaces that we use, so we can delete them afterwards - testConfiguration.hatracNamespaces.push(process.env.ERMREST_URL.replace("/ermrest", "") + "/hatrac/js/chaise/" + currentTimestampTime); -} - -var i, j, k; - -describe('Edit multiple existing record,', function() { - - for (i = 0; i < testParams.tables.length; i++) { - (function(tableParams, tableIndex, schemaName) { - var hasFile = tableParams.files && tableParams.files.length > 0; - var hasErrors = false; - var keyPairs = []; - tableParams.keys.forEach(function(key) { - keyPairs.push(key.name + key.operator + key.value); - }); - - // don't run upload test cases on ci - if (process.env.CI && hasFile) { - return; - } - - // create files - if (hasFile) { - beforeAll(function() { - recordEditHelpers.createFiles(tableParams.files); - }); - } - - describe("when the user edits " + tableParams.keys.length + " records at a time" + (hasFile ? " with files" : "") + ", ", function() { - - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/" + schemaName + ":" + tableParams.table_name + "/" + keyPairs.join(";") + "@sort(" + tableParams.sortColumns + ")"); - }); - - it("should have the title displayed properly.", function() { - let entityTitleText = 'Edit ' + tableParams.keys.length + ' ' + tableParams.table_name + ' records'; - // if submit button is visible, this means the recordedit page has loaded - chaisePage.waitForElement(element(by.id("submit-record-button"))).then(function() { - expect(chaisePage.recordEditPage.getEntityTitleElement().getText()).toBe(entityTitleText, "Multi-edit title is incorrect."); - }); - }); - - it ("should have the correct head title using the heuristics for recordedit app in entry/edit mode with multiple records", function (done) { - let headTitleText = "Edit " + tableParams.table_name; - browser.executeScript("return chaiseConfig;").then(function(chaiseConfig) { - // Edit : | Chaise - // no chaiseConfig.headTitle so use default value of Chaise - expect(browser.getTitle()).toBe(headTitleText + " | Chaise"); - - done(); - }).catch(function (err) { - console.log(err); - done.fail(); - }); - }); - - it("should have the table displayname as part of the entity title with the proper tooltip.", function() { - // if submit button is visible, this means the recordedit page has loaded - var titleLink = chaisePage.recordEditPage.getEntityTitleLinkElement(); - expect(titleLink.getText()).toBe(tableParams.table_name, "table name link is incorrect."); - - chaisePage.testTooltipReturnPromise(titleLink, tableParams.tableComment, 'recordedit'); - }); - - it("columns should have correct value, and selectable.", function() { - chaisePage.waitForElement(element(by.id("submit-record-button"))).then(function() { - for (j = 0; j < tableParams.rows.length; j++) { - var row = tableParams.rows[j]; - for (var key in row) { - let colParams = row[key]; - if (colParams.isFile) { - recordEditHelpers.testFileInput(key, j, tableParams.files[colParams.input]) - } else { - let input = chaisePage.recordEditPage.getInputForAColumn(key, j + 1); - if (colParams.isTextArea) input = chaisePage.recordEditPage.getTextAreaForAColumn(key, j+1); - // test current value - expect(input.getAttribute("value")).toBe(colParams.value, "row=" + j + ", column=" + key + " didn't have the expected value."); - - // change the value - chaisePage.recordEditPage.clearInput(input); - browser.sleep(10); - input.sendKeys(colParams.input); - - // test that value has changed - expect(input.getAttribute("value")).toBe(colParams.input, "row=" + j + ", column=" + key + " didn't get the new value."); - } - } - } - }); - - }); - - describe("Submit " + tableParams.keys.length + " records", function() { - beforeAll(function(done) { - // submit form - chaisePage.recordEditPage.submitForm(); - - - if (hasFile) { - browser.wait( - EC.invisibilityOf(element(by.css('.upload-table'))), - tableParams.files.length ? (tableParams.keys.length * tableParams.files.length * browser.params.defaultTimeout) : browser.params.defaultTimeout - ); - } - - // Make sure the table shows up with the expected # of rows - browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == tableParams.keys.length); - }); - }, browser.params.defaultTimeout); - - done(); - }); - - it("should change the view to the resultset table and verify the count.", function(done) { - browser.driver.getCurrentUrl().then(function(url) { - expect(url.startsWith(process.env.CHAISE_BASE_URL + "/recordedit/")).toBe(true); - done(); - }); - }); - - describe("result page", function () { - it("should have the correct title.", function() { - expect(chaisePage.recordEditPage.getEntityTitleElement().getText()).toBe(tableParams.results.length + " " + tableParams.table_name + " records updated successfully", "Resultset title is incorrect."); - }); - - it('should point to the correct link with caption.', function () { - var expectedLink = process.env.CHAISE_BASE_URL + "/recordset/#" + browser.params.catalogId + "/" + schemaName + ":" + tableParams.table_name; - var titleLink = chaisePage.recordEditPage.getEntityTitleLinkElement(); - - expect(titleLink.getText()).toBe(tableParams.table_name, "Title of result page doesn't have the expected caption."); - expect(titleLink.getAttribute("href")).toContain(expectedLink , "Title of result page doesn't have the expected link."); - }); - - it('should show correct table rows.', function() { - chaisePage.recordsetPage.getRows().then(function(rows) { - // same row count - expect(rows.length).toBe(tableParams.results.length, "number of rows are not as expected."); - - for (j = 0; j < rows.length; j++) { - (function(index) { - rows[index].all(by.tagName("td")).then(function(cells) { - // same column count - expect(cells.length).toBe(tableParams.results[index].length, "number of columns are not as expected."); - - var result; - - // cells is what is being shown - // tableParams.results is what we expect - for (k = 0; k < tableParams.results[index].length; k++) { - result = tableParams.results[index][k]; - - if (typeof result.link === 'string') { - expect(cells[k].element(by.tagName("a")).getAttribute("href")).toContain(result.link); - expect(cells[k].element(by.tagName("a")).getText()).toBe(result.value, "data missmatch in row with index=" + index + ", columns with index=" + k); - } else { - expect(cells[k].getText()).toBe(result, "data missmatch in row with index=" + index + ", columns with index=" + k); - } - } - }); - - })(j); - }; - }); - }); - }); - }); - }); - - if (tableParams.testFkClear) { - describe("User should be able to clear all foreign key values using multi form input,", function () { - beforeAll(function(done) { - browser.refresh(); - - chaisePage.recordeditPageReady(); - done(); - }); - - it("open the select all form and click 'clear all'", function (done) { - const toggleBtn = chaisePage.recordEditPage.getMultiFormToggleButton(tableParams.fkColumnName), - multiFormCheckbox = chaisePage.recordEditPage.getMultiFormInputCheckbox(), - multiFormClear = chaisePage.recordEditPage.getMultiFormClearBtn(); - - chaisePage.waitForElementCondition(EC.elementToBeClickable(toggleBtn)).then(() => { - return chaisePage.clickButton(toggleBtn); - }).then(() => { - return chaisePage.waitForElementCondition(EC.elementToBeClickable(multiFormCheckbox)); - }).then(() => { - // select all - return chaisePage.clickButton(multiFormCheckbox); - }).then(function () { - // clear - return chaisePage.clickButton(multiFormClear); - }).then(() => { - done(); - }).catch(chaisePage.catchTestError(done)); - }); - - it("should submit the form and show " + tableParams.keys.length + " rows updated", function (done) { - // submit form - chaisePage.recordEditPage.submitForm(); - - // Make sure the table shows up with the expected # of rows - browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == tableParams.keys.length); - }); - }, browser.params.defaultTimeout); - - expect(chaisePage.recordsetPage.getRows().count()).toBe(tableParams.keys.length, "Incorrect number of rows showing after update"); - done(); - }) - }); - } - - // delete files - if (tableParams.files && tableParams.files.length > 0) { - afterAll(function(done) { - recordEditHelpers.deleteFiles(tableParams.files); - done(); - }); - } - - })(testParams.tables[i], i, testParams.schema_name); - } -}); diff --git a/test/e2e/specs/default-config/recordedit/null-values.config.ts b/test/e2e/specs/default-config/recordedit/null-values.config.ts new file mode 100644 index 000000000..dbf04c3b1 --- /dev/null +++ b/test/e2e/specs/default-config/recordedit/null-values.config.ts @@ -0,0 +1,8 @@ +import getConfig from '@isrd-isi-edu/chaise/test/e2e/setup/playwright.configuration'; + +export default getConfig({ + testName: 'default-config/recordedit/null-values', + configFileName: 'recordedit/null-values.dev.json', + mainSpecName: 'default-config', + testMatch: [ 'null-values.spec.ts' ] +}); diff --git a/test/e2e/specs/default-config/recordedit/null-values.spec.ts b/test/e2e/specs/default-config/recordedit/null-values.spec.ts new file mode 100644 index 000000000..588e611ed --- /dev/null +++ b/test/e2e/specs/default-config/recordedit/null-values.spec.ts @@ -0,0 +1,130 @@ +/* eslint-disable max-len */ + +import { expect, test } from '@playwright/test'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +import { deleteHatracNamespaces, getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; +import { clearInputValue, createFiles, deleteFiles, setInputValue, testSubmission } from '@isrd-isi-edu/chaise/test/e2e/utils/recordedit-utils'; +import moment from 'moment'; + +const currentTimestampTimeStr = moment().format('x'); + +const longTextValue = [ + 'asjdf;laksjdf;laj ;lkajsd;f lkajsdf;lakjs f;lakjs df;lasjd f;ladsjf;alskdjfa ;lskdjf a;lsdkjf a;lskdfjal;sdkfj', + ' as;ldfkj as;dlf kjasl;fkaj;lfkjasl;fjas;ldfkjals;dfkjas;dlkfja;sldkfjasl;dkfjas;dlfkjasl;dfkja; lsdjfk a;lskdjf', + ' a;lsdfj as;ldfja;sldkfja;lskjdfa;lskdjfa;lsdkfja;sldkfjas;ldfkjas;dlfkjas;lfkja;sldkjf a;lsjf ;laskj fa;slk jfa;sld', + ' fjas;l js;lfkajs;lfkasjf;alsja;lk ;l kja' +].join(''); + +const testFiles = [ + { + name: 'testfile500kb_nulltest.png', + size: '512000', + path: 'testfile500kb_nulltest.png' + }, +] + +const testParams = { + url: 'null-values:table_1/id=1', + columns: [ + { name: 'int2_null_col', displayname: 'int2_null_col', type: RecordeditInputType.INT_2, value: '32767' }, + { name: 'int2_col', displayname: 'int2_col', type: RecordeditInputType.INT_2, value: null }, + + { name: 'int4_null_col', displayname: 'int4_null_col', type: RecordeditInputType.INT_4, value: '-2147483648' }, + { name: 'int4_col', displayname: 'int4_col', type: RecordeditInputType.INT_4, value: null }, + + { name: 'int8_null_col', displayname: 'int8_null_col', type: RecordeditInputType.INT_8, value: '9007199254740991' }, + { name: 'int8_col', displayname: 'int8_col', type: RecordeditInputType.INT_8, value: null }, + + { name: 'float4_null_col', displayname: 'float4_null_col', type: RecordeditInputType.NUMBER, value: '4.6123' }, + { name: 'float4_col', displayname: 'float4_col', type: RecordeditInputType.NUMBER, value: null }, + + { name: 'float8_null_col', displayname: 'float8_null_col', type: RecordeditInputType.NUMBER, value: '234523523.023045' }, + { name: 'float8_col', displayname: 'float8_col', type: RecordeditInputType.NUMBER, value: null }, + + { name: 'text_null_col', displayname: 'text_null_col', type: RecordeditInputType.TEXT, value: 'sample' }, + { name: 'text_col', displayname: 'text_col', type: RecordeditInputType.TEXT, value: null }, + + { name: 'longtext_null_col', displayname: 'longtext_null_col', type: RecordeditInputType.LONGTEXT, value: longTextValue }, + { name: 'longtext_col', displayname: 'longtext_col', type: RecordeditInputType.LONGTEXT, value: null }, + + { name: 'markdown_null_col', displayname: 'markdown_null_col', type: RecordeditInputType.MARKDOWN, value: 'Sample' }, + { name: 'markdown_col', displayname: 'markdown_col', type: RecordeditInputType.MARKDOWN, value: null }, + + { name: 'bool_null_col', displayname: 'bool_null_col', type: RecordeditInputType.BOOLEAN, value: 'true' }, + { name: 'bool_true_col', displayname: 'bool_true_col', type: RecordeditInputType.BOOLEAN, value: 'false' }, + { name: 'bool_false_col', displayname: 'bool_false_col', type: RecordeditInputType.BOOLEAN, value: null }, + + { name: 'timestamp_null_col', displayname: 'timestamp_null_col', type: RecordeditInputType.TIMESTAMP, value: { date_value: '2016-01-18', time_value: '13:00:00' } }, + { name: 'timestamp_col', displayname: 'timestamp_col', type: RecordeditInputType.TIMESTAMP, value: null }, + + { name: 'timestamptz_null_col', displayname: 'timestamptz_null_col', type: RecordeditInputType.TIMESTAMP, value: { date_value: '2016-01-18', time_value: '13:00:00' } }, + { name: 'timestamptz_col', displayname: 'timestamptz_col', type: RecordeditInputType.TIMESTAMP, value: null }, + + { name: 'date_null_col', displayname: 'date_null_col', type: RecordeditInputType.DATE, value: '2016-08-15' }, + { name: 'date_col', displayname: 'date_col', type: RecordeditInputType.DATE, value: null }, + + { name: 'fk_null_col', displayname: 'fk_null_col', type: RecordeditInputType.FK_POPUP, value: { modal_num_rows: 1, modal_option_index: 0 } }, + { name: 'fk_col', displayname: 'fk_col', type: RecordeditInputType.FK_POPUP, value: null }, + + { name: 'json_null_col', displayname: 'json_null_col', type: RecordeditInputType.JSON, value: '89.586' }, + { name: 'json_col', displayname: 'json_col', type: RecordeditInputType.TEXT, value: null }, + + { name: 'asset_null_col', displayname: 'asset_null_col', type: RecordeditInputType.FILE, value: testFiles[0] }, + { name: 'asset_col', displayname: 'asset_col', type: RecordeditInputType.FILE, value: null }, + + { name: 'color_rgb_hex_null_col', displayname: 'color_rgb_hex_null_col', type: RecordeditInputType.FILE, value: '#123456' }, + { name: 'color_rgb_hex_col', displayname: 'color_rgb_hex_col', type: RecordeditInputType.FILE, value: null }, + + { name: 'array_text_null_col', displayname: 'array_text_null_col', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TEXT, value: ['val1'] }, + { name: 'array_text_col', displayname: 'array_text_col', type: RecordeditInputType.ARRAY, arrayBaseType: RecordeditInputType.TEXT, value: null }, + + { name: 'timestamp_txt', displayname: 'timestamp_txt', type: RecordeditInputType.TEXT, value: currentTimestampTimeStr }, + ], + submission: { + tableDisplayname: 'table_1', + resultColumnNames: [ + 'int2_null_col', 'int4_null_col', 'int8_null_col', 'float4_null_col', 'float8_null_col', 'text_null_col', + 'longtext_null_col', 'markdown_null_col', 'bool_null_col', 'bool_true_col', + 'timestamp_null_col', 'timestamptz_null_col', 'date_null_col', 'fk_null_col', 'json_null_col', 'timestamp_txt', + 'asset_null_col', 'asset_null_col_bytes', 'array_text_null_col', 'RID', 'RCT', 'RMT', 'RCB', 'RMB' + ], + resultRowValues: [[ + '32,767', '-2,147,483,648', '9,007,199,254,740,991', '4.6123', '234,523,523.0230', 'sample', + longTextValue, 'Sample', 'true', 'false', '2016-01-18 13:00:00', '2016-01-18 13:00:00', + '2016-08-15', { caption: 'Abraham Lincoln', url: '/null-values:table_2/' }, '89.586', currentTimestampTimeStr, + { caption: 'testfile500kb_nulltest.png', url: '/hatrac/js/chaise/' + currentTimestampTimeStr }, '512 kB', 'val1' + ]] + } +}; + +test('Null values in recordedit', async ({ page, baseURL }, testInfo) => { + await test.step('create files', async () => { + await createFiles(testFiles); + }); + + await test.step('open recordedit page', async () => { + await page.goto(`${baseURL}/recordedit/#${getCatalogID(testInfo.project.name)}/${testParams.url}`); + await RecordeditLocators.waitForRecordeditPageReady(page); + }); + + await test.step('should be able to clear inputs or add values to empty inputs', async () => { + for await (const col of testParams.columns) { + await test.step(`${col.name}`, async () => { + if (col.value === null) { + await clearInputValue(page, 1, col.name, col.displayname, col.type); + } else { + await setInputValue(page, 1, col.name, col.displayname, col.type, col.value, col.arrayBaseType); + } + }); + } + }); + + await test.step('should submit and save the data', async () => { + await testSubmission(page, testParams.submission, true, 1 * 20 * 1000); + }); + + await test.step('delete create files', async () => { + await deleteFiles(testFiles); + await deleteHatracNamespaces([`/hatrac/js/chaise/${currentTimestampTimeStr}`]); + }); +}) diff --git a/test/e2e/specs/default-config/recordedit/remove-edit-form.spec.js b/test/e2e/specs/default-config/recordedit/remove-edit-form.spec.js deleted file mode 100644 index 0c144718b..000000000 --- a/test/e2e/specs/default-config/recordedit/remove-edit-form.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -var chaisePage = require('../../../utils/chaise.page.js'); -var recordEditHelpers = require('../../../utils/recordedit-helpers.js'); -var testParams = { - table_name: "multi-edit-table", - filter: { - key: "int", - operator: "=", - value: "23" - }, - original_rows: 11, - rows_after: 9, - remove_row_modal:{ - title: "Confirm Form Removal", - body: "Are you sure you want to remove this record from the edit form?\nNote: Removing a record from this form, will not delete the record from the database.", - button_text: "Remove" - } -}; - -describe('Edit a record,', function() { - - describe("For table " + testParams.table_name + ",", function() { - - var record; - - beforeAll(function () { - var filters = []; - filters.push(testParams.filter.key + testParams.filter.operator + testParams.filter.value); - chaisePage.navigate(browser.params.url + "/recordedit/#" + browser.params.catalogId + "/multi-edit:" + testParams.table_name + "/" + filters.join("&")); - chaisePage.recordeditPageReady(); - }); - - it("remove 2 forms and edit some of the data", function(done) { - // verify number of rows is what we expect - chaisePage.recordEditPage.getRecordeditForms().count().then(function(ct) { - expect(ct).toBe(testParams.original_rows, "incorrect number of rows to edit"); - - return chaisePage.recordEditPage.getAllDeleteRowButtons(); - }).then(function(buttons) { - expect(buttons.length).toBe(testParams.original_rows, "incorrect number of remove buttons"); - - // remove the 4th row - return chaisePage.recordEditPage.getDeleteRowButton(3); - }).then(function (button) { - return chaisePage.clickButton(button); - }).then(function () { - return chaisePage.recordEditPage.getRecordeditForms().count(); - }).then(function (ct) { - expect(ct).toBe(testParams.original_rows - 1, "number of rows is incorrect after removing 1"); - - // remove the 6th row (7th row in the original set) - return chaisePage.recordEditPage.getDeleteRowButton(5); - }).then(function (button) { - return chaisePage.clickButton(button); - }).then(function () { - // verify number of forms is expected - return chaisePage.recordEditPage.getRecordeditForms().count(); - }).then(function(ct) { - expect(ct).toBe(testParams.rows_after, "incorrect number of rows to edit after removing 2"); - - //change a value in 1 form - var textInput = chaisePage.recordEditPage.getInputForAColumn('text', 1); - chaisePage.recordEditPage.clearInput(textInput); - textInput.sendKeys("changed text"); - - done(); - }).catch(function (error) { - console.dir(error); - done.fail(); - }); - }); - - describe("Submit record", function() { - beforeAll(function() { - // Submit the form - chaisePage.recordEditPage.submitForm(); - }); - - - it("should change the view to the resultset table.", function() { - browser.driver.getCurrentUrl().then(function(url) { - expect(url.startsWith(process.env.CHAISE_BASE_URL + "/recordedit/")).toBe(true, "page changed away from recordedit resultset view"); - }); - }); - - it("should have the correct table rows.", function() { - browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == testParams.rows_after); - }); - }, browser.params.defaultTimeout); - - expect(chaisePage.recordsetPage.getRows().count()).toBe(testParams.rows_after, "incorrect number of rows in resultset view"); - }); - }); - - }); -}); diff --git a/test/e2e/utils/catalog-utils.ts b/test/e2e/utils/catalog-utils.ts index e96059aba..83d0e6766 100644 --- a/test/e2e/utils/catalog-utils.ts +++ b/test/e2e/utils/catalog-utils.ts @@ -1,9 +1,10 @@ import { readFileSync } from 'fs'; import { execSync } from 'child_process'; import { TestInfo } from '@playwright/test'; +import axios from 'axios'; import { isObjectAndNotNull } from '@isrd-isi-edu/chaise/src/utils/type-utils'; -import { ENTITIES_PATH } from '@isrd-isi-edu/chaise/test/e2e/utils/constants'; +import { ENTITIES_PATH, ERMREST_URL } from '@isrd-isi-edu/chaise/test/e2e/utils/constants'; // eslint-disable-next-line @typescript-eslint/no-var-requires const ermrestUtils = require('@isrd-isi-edu/ermrest-data-utils'); @@ -238,3 +239,21 @@ export const copyFileToChaiseDir = (fileLocation: string, destinationFilename: s process.exit(1); } } + +/** + * delete the hatrac namespaces that are created during testing + * @param namespaces relative paths for the namespaces starting with `/ .e.g. `/hatrac/js/chaise/some_name` + */ +export const deleteHatracNamespaces = async (namespaces: string[]) => { + const serverLocation = ERMREST_URL?.replace('ermrest', ''); + // cleanup the hatrac namespaces + for await (const ns of namespaces) { + try { + await axios(serverLocation + ns, { method: 'DELETE', headers: { Cookie: process.env.AUTH_COOKIE! } }); + console.log(`${ns} hatrac namespace deleted.`); + } catch (exp) { + console.log(`encountered an error while trying to delete hatrac namespace: ${ns}`); + console.error(exp); + } + } +} diff --git a/test/e2e/utils/page-utils.ts b/test/e2e/utils/page-utils.ts index 24e072660..5d2034d43 100644 --- a/test/e2e/utils/page-utils.ts +++ b/test/e2e/utils/page-utils.ts @@ -81,11 +81,17 @@ export async function clickAndVerifyDownload(locator: Locator, expectedFileName: /** * hover over an element and make sure it shows the expected tooltip * - * To make sure the hover goes away after this test, we're hovering over an element. - * This element is chosen based on app. + * Notes: + * - To make sure the tooltip goes away after this test, we're hovering over a different element after testing the tooltip. + * + * @param locator the element with the tooltip + * @param expectedTooltip the expected value that is used in a `toHaveText`. so if a string is passed, we expect a full match. + * @param appName the name of the app. Used for figuring out the default "hover element". + * @param hoverEl the element that we should hover over so the tooltip disapears. if undefined, we will pick a default element based on the app. */ -export async function testTooltip(locator: Locator, expectedTooltip: string | RegExp, appName: APP_NAMES, isSoft?: boolean) { +export async function testTooltip(locator: Locator, expectedTooltip: string | RegExp, appName: APP_NAMES, isSoft?: boolean, hoverEl?: Locator) { await locator.hover(); + await locator.page().pause(); const el = PageLocators.getTooltipContainer(locator.page()); @@ -95,23 +101,23 @@ export async function testTooltip(locator: Locator, expectedTooltip: string | Re await expectFn(el).toHaveText(expectedTooltip); // hover over an element that we know doesn't have tooltip to remove the tooltip - let hoverEl; - switch (appName) { - case APP_NAMES.RECORD: - hoverEl = RecordLocators.getEntityTitleElement(locator.page()); - break; - case APP_NAMES.RECORDSET: - hoverEl = RecordsetLocators.getTotalCount(locator.page()); - break; - case APP_NAMES.RECORDEDIT: - hoverEl = RecordeditLocators.getRequiredInfoEl(locator.page()); - break; - default: - // TODO what about other apps - hoverEl = locator.page().locator('.app-container'); - break; + if (!hoverEl) { + switch (appName) { + case APP_NAMES.RECORD: + hoverEl = RecordLocators.getEntityTitleElement(locator.page()); + break; + case APP_NAMES.RECORDSET: + hoverEl = RecordsetLocators.getTotalCount(locator.page()); + break; + case APP_NAMES.RECORDEDIT: + hoverEl = RecordeditLocators.getRequiredInfoEl(locator.page()); + break; + default: + // TODO what about other apps + hoverEl = locator.page().locator('.app-container'); + break; + } } - await hoverEl.hover(); await expectFn(el).not.toBeAttached(); diff --git a/test/e2e/utils/record-utils.ts b/test/e2e/utils/record-utils.ts index 7bbc74de9..83cde375e 100644 --- a/test/e2e/utils/record-utils.ts +++ b/test/e2e/utils/record-utils.ts @@ -19,8 +19,11 @@ import { /** - * TODO this function is currently only used for recordedit result test, but - * we should also use this for the 'should validate the values of each column' test in record-helpers.js + * make sure the main section of record page is showing the proper values. + * + * While `expectedColumnNames` must include all the column names, `expectedColumnValues` can be just a subset of columns. but it must + * be in the same order. so for example if you don't want to include the default system columns that are displayed at the end of the + * column list, you can omit those values. */ export const testRecordMainSectionValues = async (page: Page, expectedColumnNames: string[], expectedColumnValues: RecordsetRowValue) => { await RecordLocators.waitForRecordPageReady(page); diff --git a/test/e2e/utils/recordedit-utils.ts b/test/e2e/utils/recordedit-utils.ts index 79b7f760a..227684547 100644 --- a/test/e2e/utils/recordedit-utils.ts +++ b/test/e2e/utils/recordedit-utils.ts @@ -1,14 +1,21 @@ +import test, { expect, Locator, Page, TestInfo } from '@playwright/test'; import { execSync } from 'child_process'; import { resolve } from 'path'; -import { expect, Locator, Page } from '@playwright/test'; +import moment from 'moment'; -import { UPLOAD_FOLDER } from '@isrd-isi-edu/chaise/test/e2e/utils/constants'; -import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +// locators +import AlertLocators from '@isrd-isi-edu/chaise/test/e2e/locators/alert'; import ModalLocators from '@isrd-isi-edu/chaise/test/e2e/locators/modal'; +import PageLocators from '@isrd-isi-edu/chaise/test/e2e/locators/page'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; import RecordsetLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordset'; + +// utils +import { APP_NAMES, UPLOAD_FOLDER } from '@isrd-isi-edu/chaise/test/e2e/utils/constants'; import { RecordsetRowValue, testRecordsetTableRowValues } from '@isrd-isi-edu/chaise/test/e2e/utils/recordset-utils'; -import AlertLocators from '@isrd-isi-edu/chaise/test/e2e/locators/alert'; import { testRecordMainSectionValues } from '@isrd-isi-edu/chaise/test/e2e/utils/record-utils'; +import { clickNewTabLink, testTooltip } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils'; +import { getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; export type RecordeditExpectedColumn = { name: string, @@ -42,7 +49,8 @@ export type RecordeditFile = { size: number | string, path: string, skipCreation?: boolean, - skipDeletion?: boolean + skipDeletion?: boolean, + tooltip?: string } /** @@ -58,7 +66,7 @@ export const createFiles = async (files: RecordeditFile[]) => { if (!f.skipCreation) { const path = resolve(UPLOAD_FOLDER, f.path); execSync(`mkdir -p ${UPLOAD_FOLDER}`); - execSync(`perl -e 'print \"1\" x ${f.size}' > ${path}`); + execSync(`perl -e 'print \'1\' x ${f.size}' > ${path}`); console.log(`${path} created`); } } @@ -90,6 +98,11 @@ export const selectFile = async (file: RecordeditFile, fileInputBtn: Locator, fi const fileChooser = await fileChooserPromise; await fileChooser.setFiles(resolve(UPLOAD_FOLDER, file.path)); await expect.soft(fileTextInput).toHaveText(file.name); + + // TODO why is this not working? + // if (file.tooltip) { + // await testTooltip(fileTextInput, file.tooltip, APP_NAMES.RECORDEDIT, true); + // } } @@ -113,37 +126,66 @@ type SetInputValueProps = string | RecordeditFile | { date_value: string, time_value: string } | { + /** + * how many rows are visible in the initial fk picker. + */ modal_num_rows: number, - modal_option_index: number + /** + * the index of the option that we should choose + */ + modal_option_index: number, + /** + * the rowname of the selected option + */ + rowName?: string }; /** + * clear the value of an input by clicking on the "x" button. * - * expected types: 'timestamp', 'boolean', 'fk', 'fk-dropdown', 'array' , or any other string - * - * NOTE: 'array' only supports array of texts for now. - * - * expected valueProps: - * { - * - * // general: - * value, - * - * // time stamp props: - * date_value, - * time_value, - * - * // fk props: - * modal_num_rows, - * modal_option_index, + * Notes: + * - this function assumes the input already has a value and doesn't double check. +* - in most cases it will click on the "x" for the input. + */ +export const clearInputValue = async ( + page: Page, formNumber: number, name: string, displayname: string, inputType: RecordeditInputType, +) => { + switch (inputType) { + case RecordeditInputType.FK_POPUP: + const fkBtn = RecordeditLocators.getForeignKeyInputClear(page, displayname, formNumber); + await fkBtn.click(); + break; + case RecordeditInputType.TIMESTAMP: + const timestampBtns = RecordeditLocators.getTimestampInputsForAColumn(page, name, formNumber); + // we have to remove the date first, otherwise time will keep showing 00:00:00 + await timestampBtns.dateRemoveBtn.click(); + await timestampBtns.timeRemoveBtn.click(); + break; + case RecordeditInputType.ARRAY: + const elems = RecordeditLocators.getArrayFieldElements(page, name, formNumber); + while (await elems.removeItemButtons.count() > 0) { + await elems.removeItemButtons.nth(0).click(); + } + break; + default: + const btn = RecordeditLocators.getInputRemoveButton(page, name, formNumber); + await btn.click(); + break; + } +} + +/** + * chagne the input value. * - * } + * Notes: + * - If the given value is invalid, this test will not proceed. + * - If you want to clear an input, we recommend calling `clearInputValue` instead. * * @returns */ export const setInputValue = async ( page: Page, formNumber: number, name: string, displayname: string, inputType: RecordeditInputType, - valueProps: SetInputValueProps | SetInputValueProps[] + valueProps: SetInputValueProps | SetInputValueProps[], arrayBaseType?: RecordeditInputType ) => { switch (inputType) { case RecordeditInputType.BOOLEAN: @@ -154,6 +196,19 @@ export const setInputValue = async ( await expect.soft(RecordeditLocators.getDropdownText(dropdown)).toHaveText(valueProps); break; + case RecordeditInputType.COLOR: + if (typeof valueProps !== 'string') return; + const colorInput = RecordeditLocators.getInputForAColumn(page, name, formNumber); + await colorInput.clear(); + await colorInput.fill(valueProps); + // the input won't validate until we press enter or change focus + await RecordeditLocators.getRequiredInfoEl(page).focus(); + // make sure the displayed value is correct + await expect.soft(colorInput).toHaveValue(valueProps); + // make sure the background color is correct + expect.soft(await RecordeditLocators.getColorInputBackground(page, name, formNumber)).toEqual(valueProps); + break; + case RecordeditInputType.FK_POPUP: if (typeof valueProps !== 'object' || !(('modal_num_rows' in valueProps) && ('modal_option_index' in valueProps))) { return; @@ -165,6 +220,11 @@ export const setInputValue = async ( await expect.soft(RecordsetLocators.getRows(rsModal)).toHaveCount(valueProps.modal_num_rows); await RecordsetLocators.getRowSelectButton(rsModal, valueProps.modal_option_index).click(); await expect.soft(rsModal).not.toBeAttached(); + + if (valueProps.rowName) { + await expect.soft(RecordeditLocators.getForeignKeyInputDisplay(page, displayname, formNumber)).toHaveText(valueProps.rowName); + } + break; case RecordeditInputType.FK_DROPDOWN: @@ -198,11 +258,17 @@ export const setInputValue = async ( const fileInputBtn = RecordeditLocators.getFileInputButtonForAColumn(page, name, formNumber); const fileTextInput = RecordeditLocators.getTextFileInputForAColumn(page, name, formNumber); await selectFile(valueProps, fileInputBtn, fileTextInput); + + if (valueProps.tooltip) { + // when we select a file, the file button remains in focus, so we have to hover on some other element first + await RecordeditLocators.getRequiredInfoEl(page).hover(); + await testTooltip(fileInputBtn, valueProps.tooltip, APP_NAMES.RECORDEDIT, true); + } break; case RecordeditInputType.ARRAY: - if (!Array.isArray(valueProps)) return; - const elems = RecordeditLocators.getArrayFieldElements(page, name, formNumber, 'text'); + if (!Array.isArray(valueProps) || arrayBaseType === undefined) return; + const elems = RecordeditLocators.getArrayFieldElements(page, name, formNumber); // remove the existing value if there are any while (await elems.removeItemButtons.count() > 0) { @@ -211,8 +277,13 @@ export const setInputValue = async ( // add the values one by one. for (const val of valueProps) { - if (typeof val !== 'string') continue; - await elems.addItemInput.fill(val); + const addItemName = RecordeditLocators.getArrayInputName(name, -1); + const addOrDiscardMessage = 'Click \'Add\' to include the value or clear the entry to discard.'; + const addItemError = RecordeditLocators.getErrorMessageForAColumn(page, addItemName, formNumber, true); + + await setInputValue(page, formNumber, addItemName, displayname, arrayBaseType, val); + await expect.soft(addItemError).toBeVisible(); + await expect.soft(addItemError).toHaveText(addOrDiscardMessage); await elems.addItemButton.click(); } break; @@ -228,6 +299,96 @@ export const setInputValue = async ( } }; +/** + * test the value diplayed for a input on the recordedit form + */ +export const testInputValue = async ( + page: Page, formNumber: number, name: string, displayname: string, inputType: RecordeditInputType, + disabled: boolean, valueProps?: SetInputValueProps | SetInputValueProps[], arrayBaseType?: RecordeditInputType +) => { + let input; + const inputControl = RecordeditLocators.getInputControlForAColumn(page, name, formNumber); + switch (inputType) { + case RecordeditInputType.BOOLEAN: + input = RecordeditLocators.getDropdownElementByName(page, name, formNumber); + await expect.soft(input).toBeVisible(); + if (disabled) { + await expect.soft(inputControl).toHaveClass(/input-disabled/); + } + + if (typeof valueProps !== 'string') return; + await expect.soft(input).toHaveText(valueProps); + break; + + case RecordeditInputType.COLOR: + input = RecordeditLocators.getColorInputForAColumn(page, name, formNumber); + await expect.soft(input).toBeVisible(); + + if (typeof valueProps !== 'string') return; + // make sure the displayed value is correct + await expect.soft(input).toHaveValue(valueProps); + // make sure the background color is correct + expect.soft(await RecordeditLocators.getColorInputBackground(page, name, formNumber)).toEqual(valueProps); + break; + + case RecordeditInputType.FK_POPUP: + input = RecordeditLocators.getForeignKeyInputDisplay(page, displayname, formNumber); + await expect.soft(input).toBeVisible(); + if (disabled) { + await expect.soft(input).toHaveClass(/input-disabled/); + } + + if (typeof valueProps !== 'string') return; + await expect.soft(input).toHaveText(valueProps); + break; + + case RecordeditInputType.TIMESTAMP: + input = RecordeditLocators.getTimestampInputsForAColumn(page, name, formNumber); + await expect.soft(input.date).toBeVisible(); + await expect.soft(input.time).toBeVisible(); + if (disabled) { + await expect.soft(input.date).toBeDisabled(); + await expect.soft(input.time).toBeDisabled(); + } + + if (typeof valueProps !== 'object' || !('date_value' in valueProps) || !('time_value' in valueProps)) return; + await expect.soft(input.date).toHaveValue(valueProps.date_value); + await expect.soft(input.time).toHaveValue(valueProps.time_value); + break; + + case RecordeditInputType.FILE: + input = RecordeditLocators.getTextFileInputForAColumn(page, name, formNumber); + await expect.soft(input).toBeVisible(); + if (disabled) { + await expect.soft(inputControl).toHaveClass(/input-disabled/); + } + + if (typeof valueProps !== 'string') return; + await expect.soft(input).toHaveText(valueProps); + break; + + case RecordeditInputType.ARRAY: + if (!Array.isArray(valueProps) || arrayBaseType === undefined) return; + + for (const [index, val] of valueProps.entries()) { + const itemName = RecordeditLocators.getArrayInputName(name, index); + await testInputValue(page, formNumber, itemName, displayname, arrayBaseType, disabled, val); + } + break; + + default: + input = RecordeditLocators.getInputForAColumn(page, name, formNumber); + await expect.soft(input).toBeVisible(); + if (disabled) { + await expect.soft(input).toBeDisabled(); + } + + if (typeof valueProps !== 'string') return; + await expect.soft(input).toHaveValue(valueProps); + break; + } +} + /** * test the values displayed on the forms for a column * @@ -238,99 +399,26 @@ export const setInputValue = async ( * * NOTE: 'array' only supports array of texts for now. * - * @param {string} name the column name - * @param {string}}displayname the column displayname - * @param {string} displayType the display type (boolean, fk, timestamp, upload, "any other string") - * @param {boolean} allDisabled whether we should test that all the inputs are disabled or not - * @param {any[]} expectedValues the expected values + * @param name the column name + * @param displayname the column displayname + * @param displayType the display type (boolean, fk, timestamp, upload, 'any other string') + * @param allDisabled whether we should test that all the inputs are disabled or not + * @param expectedValues the expected values * @returns */ export const testFormValuesForAColumn = async ( page: Page, name: string, displayname: string, inputType: RecordeditInputType, allDisabled: boolean, - expectedValues: (SetInputValueProps |SetInputValueProps[])[] + expectedValues: (SetInputValueProps | SetInputValueProps[])[] ) => { - - let formNumber = 1, input; - const inputControl = RecordeditLocators.getInputControlForAColumn(page, name, formNumber); + let formNumber = 1; for (const value of expectedValues) { - switch (inputType) { - case RecordeditInputType.BOOLEAN: - if (typeof value !== 'string') return; - - input = RecordeditLocators.getDropdownElementByName(page, name, formNumber); - if (allDisabled) { - await expect.soft(inputControl).toHaveClass(/input-disabled/); - } - await expect.soft(input).toHaveText(value); - break; - - case RecordeditInputType.FK_POPUP: - if (typeof value !== 'string') return; - - input = RecordeditLocators.getForeignKeyInputDisplay(page, displayname, formNumber); - if (allDisabled) { - await expect.soft(input).toHaveClass(/input-disabled/); - } - await expect.soft(input).toHaveText(value); - break; - - case RecordeditInputType.TIMESTAMP: - if (typeof value !== 'object' || !('date_value' in value) || !('time_value' in value)) return; - input = RecordeditLocators.getTimestampInputsForAColumn(page, name, formNumber); - if (allDisabled) { - await expect.soft(input.date).toBeDisabled(); - await expect.soft(input.time).toBeDisabled(); - } - await expect.soft(input.date).toHaveValue(value.date_value); - await expect.soft(input.time).toHaveValue(value.time_value); - break; - - case RecordeditInputType.FILE: - if (typeof value !== 'string') return; - - input = RecordeditLocators.getTextFileInputForAColumn(page, name, formNumber); - if (allDisabled) { - await expect.soft(inputControl).toHaveClass(/input-disabled/); - } - await expect.soft(input).toHaveText(value); - break; - - case RecordeditInputType.ARRAY: - if (!Array.isArray(value)) return; - const elems = RecordeditLocators.getArrayFieldElements(page, name, formNumber, 'text'); - - let index = 0; - for (const val of value) { - if (typeof val !== 'string') continue; - input = elems.inputs.nth(index); - if (allDisabled) { - await expect.soft(input).toBeDisabled(); - } - await expect.soft(input).toHaveValue(val); - index++; - } - - break; - - default: - if (typeof value !== 'string') return; - - input = RecordeditLocators.getInputForAColumn(page, name, formNumber); - if (allDisabled) { - await expect.soft(input).toBeDisabled(); - } - await expect.soft(input).toHaveValue(value); - break; - } - + await testInputValue(page, formNumber, name, displayname, inputType, allDisabled, value); formNumber++; } - - }; -type TestSubmissionParams = { +export type TestSubmissionParams = { tableDisplayname: string, resultColumnNames: string[], /** @@ -342,7 +430,14 @@ type TestSubmissionParams = { export const testSubmission = async (page: Page, params: TestSubmissionParams, isEditMode?: boolean, timeout?: number) => { await RecordeditLocators.getSubmitRecordButton(page).click(); - await expect.soft(AlertLocators.getErrorAlert(page)).not.toBeAttached(); + + try { + await expect(AlertLocators.getErrorAlert(page)).not.toBeAttached(); + } catch (exp) { + // provide more information about what went wrong + const alertContent = await AlertLocators.getErrorAlert(page).textContent(); + expect(alertContent).toEqual(''); + } await expect.soft(ModalLocators.getUploadProgressModal(page)).not.toBeAttached({ timeout: timeout }); await expect.soft(RecordeditLocators.getSubmitSpinner(page)).not.toBeAttached({ timeout: timeout }); @@ -361,3 +456,624 @@ export const testSubmission = async (page: Page, params: TestSubmissionParams, i await testRecordsetTableRowValues(resultset, params.resultRowValues, true); } } + +/** + * run the validation tests and make sure extra features for the input work + * + * used by testFormPresentationAndValidation. I extracted this into a function so we can recursively call it for arrays. + * @private + */ +const _testInputValidationAndExtraFeatures = async ( + page: Page, formNumber: number, name: string, displayname: string, inputType: RecordeditInputType, arrayBaseType?: RecordeditInputType, + existingValue?: SetInputValueProps | SetInputValueProps[], fkPopupTitle?: string +) => { + const cellError = RecordeditLocators.getErrorMessageForAColumn(page, name, formNumber); + + switch (inputType) { + case RecordeditInputType.ARRAY: + const itemName = RecordeditLocators.getArrayInputName(name, -1); + if (arrayBaseType) { + await _testInputValidationAndExtraFeatures(page, formNumber, itemName, displayname, arrayBaseType); + } + break; + + case RecordeditInputType.JSON: + case RecordeditInputType.JSONB: + const jsonInput = RecordeditLocators.getInputForAColumn(page, name, formNumber); + + await test.step('should allow any valid JSON values.', async () => { + const validJSONValues = [ + { stringVal: '{}', description: 'empty object' }, + { stringVal: '{\"name\":\"tester\"}', description: 'object' }, + { stringVal: '6534.9987', description: 'number' }, + { stringVal: 'null', description: 'null' }, + { stringVal: '\" \"', description: 'string of spaces' } + ]; + + for await (const val of validJSONValues) { + await jsonInput.clear(); + await jsonInput.fill(val.stringVal); + await expect.soft(jsonInput, val.description).toHaveValue(val.stringVal); + await expect.soft(cellError, val.description).not.toBeAttached(); + await jsonInput.clear(); + } + }); + + await test.step('should not allow invalid JSON values', async () => { + const invalidJSONValues = [ + { stringVal: '{', description: 'only {' }, + { stringVal: '{name\":\"tester\"}', description: 'missing double qoute' }, + { stringVal: ' ', description: 'empty' } + ]; + + for await (const val of invalidJSONValues) { + await jsonInput.clear(); + await jsonInput.fill(val.stringVal); + await expect.soft(jsonInput, val.description).toHaveValue(val.stringVal); + await expect.soft(cellError, val.description).toBeVisible(); + await expect.soft(cellError, val.description).toHaveText('Please enter a valid JSON value.'); + await jsonInput.clear(); + } + }); + break; + + case RecordeditInputType.MARKDOWN: + const markdownProps = RecordeditLocators.getMarkdownElements(page, name, formNumber); + + await test.step('should render markdown with inline preview and full preview button.', async () => { + const markdownTestParams = [{ + input: 'RBK Project ghriwvfw nwoeifwiw qb2372b wuefiquhf pahele kabhi na phelke kabhiy gqeequhwqh', + html: '

RBK Project ghriwvfw nwoeifwiw qb2372b wuefiquhf pahele kabhi na phelke kabhiy gqeequhwqh

\n', + title: 'Heading' + }, { + input: 'E15.5 embryonic kidneys for sections\n' + + '- E18.5 embryonic kidneys for cDNA synthesis\n' + + '- Sterile PBS\n' + + '- QIAShredder columns (Qiagen, cat no. 79654)\n' + + '- DEPC-Treated Water', + html: '
    \n' + + '
  • E15.5 embryonic kidneys for sections
  • \n' + + '
  • E18.5 embryonic kidneys for cDNA synthesis
  • \n' + + '
  • Sterile PBS
  • \n' + + '
  • QIAShredder columns (Qiagen, cat no. 79654)
  • \n' + + '
  • DEPC-Treated Water
  • \n' + + '
\n', + title: 'Unordered List' + }, { + // eslint-disable-next-line max-len + input: 'This is bold text. nuf2uh3498hcuh23uhcu29hh nfwnfi2nfn k2mr2ijri. Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru', + // eslint-disable-next-line max-len + html: '

This is bold text. nuf2uh3498hcuh23uhcu29hh nfwnfi2nfn k2mr2ijri. Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru

\n', + title: 'Bold' + }, { + // eslint-disable-next-line max-len + input: 'This is italic text fcj2ij3ijjcn 2i3j2ijc3roi2joicj. Hum ja rahal chi gaam ta pher kail aaib. Khana kha ka aib rehal chi parson tak.', + // eslint-disable-next-line max-len + html: '

This is italic text fcj2ij3ijjcn 2i3j2ijc3roi2joicj. Hum ja rahal chi gaam ta pher kail aaib. Khana kha ka aib rehal chi parson tak.

\n', + title: 'Italic' + }, { + input: '~~Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru~~', + html: '

Strikethrough wnnfw nwn wnf wu2h2h3hr2hrf13hu u 2u3h u1ru31r 1n3r uo13ru1ru

\n', + title: '' + }, { + input: 'X^2^+Y^2^+Z^2^=0', + html: '

X2+Y2+Z2=0

\n', + title: '' + }, { + input: '[[RID]]', + html: '

RID

\n', + title: '' + }]; + + const mdInput = RecordeditLocators.getInputForAColumn(page, name, formNumber); + await mdInput.clear(); + for await (const param of markdownTestParams) { + //if title defined found for markdown elements then send click command + if (param.title) { + const mdOption = markdownProps.getButton(param.title); + await expect.soft(mdOption).toBeVisible(); + await mdOption.click(); + } + + // .fill will replace the content but we want to append to the content, so using .pressSequentially instead + await mdInput.pressSequentially(param.input); + + // inline preview + await markdownProps.previewButton.click(); + await expect.soft(markdownProps.previewContent).toBeVisible(); + expect.soft(await markdownProps.previewContent.innerHTML()).toEqual(param.html); + await markdownProps.previewButton.click(); + await expect.soft(markdownProps.previewContent).not.toBeAttached(); + + // modal preview + const modal = ModalLocators.getMarkdownPreviewModal(page); + await markdownProps.fullScreenButton.click(); + await expect.soft(modal).toBeVisible(); + const modalContent = ModalLocators.getMarkdownPreviewContent(modal); + await expect.soft(modalContent).toBeVisible(); + expect.soft(await modalContent.innerHTML()).toEqual(param.html); + + await ModalLocators.getCloseBtn(modal).click(); + await expect.soft(modal).not.toBeAttached(); + + await mdInput.clear(); + } + }); + + await test.step('help button should open the help page', async () => { + const newPage = await clickNewTabLink(markdownProps.helpButton); + await newPage.waitForURL('**/help/?page=chaise%2Fmarkdown-help'); + await expect.soft(PageLocators.getHelpPageMainTable(newPage)).toBeVisible(); + await newPage.close(); + }); + break; + + case RecordeditInputType.FK_POPUP: + const displayedValue = RecordeditLocators.getForeignKeyInputDisplay(page, displayname, formNumber); + + if (typeof existingValue === 'string') { + await test.step('clicking the "x" should remove the value in the foreign key field.', async () => { + await expect.soft(displayedValue).toHaveText(existingValue); + await RecordeditLocators.getForeignKeyInputClear(page, displayname, formNumber).click(); + await expect.soft(displayedValue).toHaveText('Select a value'); + }); + } + + await test.step('popup selector', async () => { + const rsModal = ModalLocators.getForeignKeyPopup(page); + await test.step('should have the proper title.', async () => { + await RecordeditLocators.getForeignKeyInputButton(page, displayname, formNumber).click(); + await expect.soft(rsModal).toBeVisible(); + await expect.soft(RecordsetLocators.getRows(rsModal)).not.toHaveCount(0); + if (fkPopupTitle) { + await expect.soft(ModalLocators.getModalTitle(rsModal)).toContainText(fkPopupTitle); + } + }); + + await test.step('closing without selecting should not select any values.', async () => { + await ModalLocators.getCloseBtn(rsModal).click(); + await expect.soft(rsModal).not.toBeAttached(); + await expect.soft(displayedValue).toHaveText('Select a value'); + }); + }); + break; + + case RecordeditInputType.DATE: + const dateInputProps = RecordeditLocators.getDateInputsForAColumn(page, name, formNumber); + const dateRemoveBtn = RecordeditLocators.getInputRemoveButton(page, name, formNumber); + + await test.step('should complain about invalid date values.', async () => { + // testing partial input + await dateInputProps.date.clear(); + await dateInputProps.date.fill('1234-1'); + await expect.soft(cellError).toHaveText('Please enter a valid date value in YYYY-MM-DD format.'); + + // clear the input and see if the error disapears + // (.clear() wasn't working consistently) + await dateRemoveBtn.click(); + await expect.soft(cellError).not.toBeAttached(); + }); + + await test.step('"Today" button should enter the current date into the input', async () => { + await dateInputProps.todayBtn.click(); + await expect.soft(dateInputProps.date).toHaveValue(moment().format('YYYY-MM-DD')); + }); + + await test.step('"clear" button should clear the date.', async () => { + await expect.soft(dateRemoveBtn).toBeVisible(); + await dateRemoveBtn.click(); + await expect.soft(dateInputProps.date).toHaveValue(''); + await expect.soft(dateRemoveBtn).not.toBeAttached(); + }); + + break; + + case RecordeditInputType.TIMESTAMP: + const timestampProps = RecordeditLocators.getTimestampInputsForAColumn(page, name, formNumber); + const timeErrorMessage = 'Please enter a valid time value in 24-hr HH:MM:SS format.'; + const dateErrorMessage = 'Please enter a valid date value in YYYY-MM-DD format.'; + + await test.step('should complain about invalid values and allow valid ones.', async () => { + await timestampProps.date.clear(); + await timestampProps.time.clear(); + + // the test cases below are only checking time, so we should first add a proper date. + await timestampProps.date.fill('2016-01-01'); + + // If user enters an invalid time an error msg should appear + await timestampProps.time.fill('24:12:00'); + await expect.soft(cellError).toHaveText(timeErrorMessage); + + // If user enters a valid time, then error msg should disappear + await timestampProps.time.fill('12:00:00'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter 1 digit in any place + await timestampProps.time.clear(); + await timestampProps.time.fill('2:2:2'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter just the hours + await timestampProps.time.clear(); + await timestampProps.time.fill('08'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter just the hours and minutes + await timestampProps.time.clear(); + await timestampProps.time.fill('2:10'); + await expect.soft(cellError).not.toBeAttached(); + + // users can enter 0 for the time + await timestampProps.time.clear(); + await timestampProps.time.fill('00:00:00'); + await expect.soft(cellError).not.toBeAttached(); + + // Invalid date + good time = error + // If user enters a valid time but no date, an error msg should appear + await timestampProps.date.clear(); + await timestampProps.time.clear(); + await timestampProps.time.fill('12:00:00'); + await expect.soft(cellError).toHaveText(dateErrorMessage); + + // Good date + good time = no error + // Now, if user enters a valid date, then no error message should appear + await timestampProps.date.fill('2016-01-01'); + await expect.soft(cellError).not.toBeAttached(); + + // Good date + clear time = no error + await timestampProps.time.clear(); + await expect.soft(cellError).not.toBeAttached(); + + }); + + await test.step('"Now" button should enter the current date and time', async () => { + const nowObject = moment(); + const nowDate = nowObject.format('YYYY-MM-DD'); + await timestampProps.nowBtn.click(); + await expect.soft(timestampProps.date).toHaveValue(nowDate); + + await expect.soft(timestampProps.time).not.toHaveValue(''); + const UITime = await timestampProps.time.getAttribute('value') as string; + const UIObject = moment(nowDate + UITime, 'YYYY-MM-DDhh:mm'); + expect.soft(UIObject.diff(nowObject, 'minutes')).toEqual(0); + }); + + await test.step('"clear" button should clear both time and date.', async () => { + await timestampProps.clearBtn.click(); + await expect.soft(timestampProps.date).toHaveValue(''); + await expect.soft(timestampProps.time).toHaveValue(''); + }); + + break; + + case RecordeditInputType.INT_2: + case RecordeditInputType.INT_4: + case RecordeditInputType.INT_8: + const intInput = RecordeditLocators.getInputForAColumn(page, name, formNumber); + + await test.step('should complain about invalid values and allow valid ones.', async () => { + // a non-number value + await intInput.clear(); + await intInput.fill('1j2yu'); + await expect.soft(cellError).toHaveText('Please enter a valid integer value.'); + + await intInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + + // float number + await intInput.fill('12.1'); + await expect.soft(cellError).toHaveText('Please enter a valid integer value.'); + + await intInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + + // min and max values + let invalidMaxNo = '2343243243242414423243242353253253253252352', invalidMinNo = '-2343243243242414423243242353253253253252352'; + if (inputType === RecordeditInputType.INT_2) { + invalidMaxNo = '8375832757832', invalidMinNo = '-237587565'; + } else if (inputType === RecordeditInputType.INT_4) { + invalidMaxNo = '3827374576453', invalidMinNo = '-326745374576375'; + } + + await intInput.clear(); + await intInput.fill(invalidMaxNo); + await expect.soft(cellError).toContainText('This field requires a value less than'); + + await intInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + + await intInput.fill(invalidMinNo); + await expect.soft(cellError).toContainText('This field requires a value greater than'); + + await intInput.clear(); + await intInput.fill('12') + await expect.soft(cellError).not.toBeAttached(); + + await intInput.clear(); + }); + break; + + case RecordeditInputType.NUMBER: + const numInput = RecordeditLocators.getInputForAColumn(page, name, formNumber); + + await test.step('should complain about invalid values and allow valid ones.', async () => { + // a non-number value + await numInput.clear(); + await numInput.fill('1j2yu'); + await expect.soft(cellError).toHaveText('Please enter a valid decimal value.'); + + // float number + await numInput.clear(); + await numInput.fill('12.1'); + await expect.soft(cellError).not.toBeAttached(); + + // int number + await numInput.clear(); + await numInput.fill('12') + await expect.soft(cellError).not.toBeAttached(); + + await numInput.clear(); + await expect.soft(cellError).not.toBeAttached(); + }); + break; + + case RecordeditInputType.COLOR: + const colorInput = RecordeditLocators.getInputForAColumn(page, name, formNumber); + + await test.step('should now allow invalid values and allow valid ones.', async () => { + // an incomplete value + await colorInput.clear(); + await colorInput.fill('#de'); + // the input won't validate until we focus somewhere else + await RecordeditLocators.getRequiredInfoEl(page).click(); + await expect.soft(colorInput).toHaveValue(typeof existingValue === 'string' ? existingValue : '#'); + + // a valid color + await colorInput.clear(); + await colorInput.fill('#abcabc'); + await expect.soft(colorInput).toHaveValue('#abcabc'); + await expect.soft(cellError).not.toBeAttached(); + }); + + await test.step('color picker popup', async () => { + // focus somewhere else to make sure the popup is still not open + await RecordeditLocators.getRequiredInfoEl(page).click(); + // open the popup + await RecordeditLocators.getColorInputBtn(page, name, formNumber).click(); + + const colorPopup = RecordeditLocators.getColorInputPopup(page); + await expect.soft(colorPopup).toBeVisible(); + + // make sure clear btn is offered regardless of null/not-null (just like any other input) + const colorClearBtn = RecordeditLocators.getColorInputPopupClearBtn(page); + await expect.soft(colorClearBtn).toBeVisible(); + + const colorPopupInput = RecordeditLocators.getColorInputPopupInput(page); + await expect.soft(colorPopupInput).toBeVisible(); + + // make sure users can use the color popup input + const colorVal = '#555555'; + await colorPopupInput.clear(); + await colorPopupInput.fill(colorVal); + await RecordeditLocators.getColorInputPopupSelectBtn(page).click(); + await expect.soft(colorPopup).not.toBeAttached(); + + await expect.soft(colorInput).toHaveValue(colorVal); + expect.soft(await RecordeditLocators.getColorInputBackground(page, name, formNumber)).toEqual(colorVal); + + }); + + break; + } +} + + +export type TestFormPresentationAndValidation = { + /** + * describe what this test is about. + */ + description: string, + schemaName: string, + tableName: string, + tableDisplayname: string, + tableComment?: string, + /** + * applicaple only to edit mode + */ + rowNames?: string[], + + columns: { + name: string, + displayname: string, + type: RecordeditInputType, + arrayBaseType?: RecordeditInputType, + isRequired?: boolean, + comment?: string, + inlineComment?: string, + disabled?: boolean, + skipValidation?: boolean, + }[], + + /** + * the new values that should be used for the inputs. + * + */ + inputs: { + [colName: string]: SetInputValueProps | SetInputValueProps[] + }[], + + /** + * existing values in the form + */ + values?: { + [colName: string]: SetInputValueProps | SetInputValueProps[] + }[] +} + +/** + * can be used to test the recordedit page. works for single or multi, create or edit. + * + * Notes: + * - If the input has an existing value, and no input, we will skip the validation as it will manipulate the value. + */ +export const testFormPresentationAndValidation = async ( + page: Page, baseURL: string | undefined, testInfo: TestInfo, params: TestFormPresentationAndValidation, isEditMode?: boolean +) => { + + const _getColumnValue = (recordIndex: number, colName: string) => { + if (Array.isArray(params.values) && params.values.length > recordIndex && typeof params.values[recordIndex][colName] !== 'undefined') { + return params.values[recordIndex][colName]; + } + return undefined; + }; + + const _getColumnInput = (recordIndex: number, colName: string) => { + if (Array.isArray(params.inputs) && params.inputs.length > recordIndex && typeof params.inputs[recordIndex][colName] !== 'undefined') { + return params.inputs[recordIndex][colName]; + } + return undefined; + } + + await test.step('should have the correct page title.', async () => { + await RecordeditLocators.waitForRecordeditPageReady(page); + + let pageTitle; + if (isEditMode && params.rowNames && params.rowNames.length === 1) { + pageTitle = `Edit ${params.tableDisplayname}: ${params.rowNames[0]}`; + } else { + const appendText = params.inputs.length > 1 ? 'records' : 'record'; + const action = isEditMode ? 'Edit' : 'Create'; + pageTitle = `${action} ${params.inputs.length} ${params.tableDisplayname} ${appendText}`; + } + + const titleEl = RecordeditLocators.getPageTitle(page); + await expect.soft(titleEl).toHaveText(pageTitle); + + const linkEl = RecordeditLocators.getPageTitleLink(page); + const expectedLink = `${baseURL}/recordset/#${getCatalogID(testInfo.project.name)}/${params.schemaName}:${params.tableName}?pcid=`; + + expect.soft(await linkEl.getAttribute('href')).toContain(expectedLink); + + if (params.tableComment) { + /** + * the default "hover element" that this function will use for recordedit is the "required info" which is directly below the title. + * when the title tooltip is displayed, the tooltip is blocking the "required info", that's why in here we're passing our own hover element. + */ + await testTooltip(linkEl, params.tableComment, APP_NAMES.RECORDEDIT, true, titleEl); + } + }); + + await test.step('should have the corret head title.', async () => { + let pageTitle; + if (isEditMode) { + pageTitle = `Edit ${params.tableDisplayname}`; + if (params.rowNames && params.rowNames.length === 1) { + pageTitle += `: ${params.rowNames[0]}`; + } + } else { + pageTitle = `Create new ${params.tableDisplayname}`; + } + expect.soft(await page.title()).toContain(pageTitle + ' | '); + }); + + await test.step('should show the proper buttons.', async () => { + if (isEditMode) { + await expect.soft(RecordeditLocators.getRecordeditResetButton(page)).toBeVisible(); + } else { + await expect.soft(RecordeditLocators.getCloneFormInputSubmitButton(page)).toBeVisible(); + } + const submitBtn = RecordeditLocators.getSubmitRecordButton(page); + await expect.soft(submitBtn).toBeVisible(); + await expect.soft(submitBtn).toHaveText('Save'); + }); + + await test.step('should show the columns in the expected order and mark the required ones.', async () => { + const columns = RecordeditLocators.getAllColumnNames(page); + await expect.soft(columns).toHaveCount(params.columns.length); + + for await (const [index, expectedCol] of params.columns.entries()) { + const col = columns.nth(index); + await expect.soft(col).toHaveText(expectedCol.displayname); + const requiredIcon = RecordeditLocators.getColumnRequiredIcon(col); + const errorMessage = `missmatch required status, index=${index}, name=${expectedCol.name}`; + if (expectedCol.isRequired) { + await expect.soft(requiredIcon, errorMessage).toBeVisible(); + } else { + await expect.soft(requiredIcon, errorMessage).not.toBeVisible(); + } + } + }); + + await test.step('should properly show the column tooltips and inline tooltips.', async () => { + const expectedColsWTooltip: number[] = []; + const expectedInlineComments: string[] = []; + params.columns.forEach((c, i) => { + if (c.comment) { + expectedColsWTooltip.push(i); + } + else if (c.inlineComment) { + expectedInlineComments.push(c.inlineComment); + } + }); + + // inline comments + const inlineComments = RecordeditLocators.getColumnInlineComments(page); + await expect.soft(inlineComments).toHaveCount(expectedInlineComments.length); + await expect.soft(inlineComments).toHaveText(expectedInlineComments); + + // tooltips + const colsWithTooltip = RecordeditLocators.getColumnNamesWithTooltip(page); + await expect.soft(colsWithTooltip).toHaveCount(expectedColsWTooltip.length); + for (const i of expectedColsWTooltip) { + await testTooltip(RecordeditLocators.getColumnNameByColumnIndex(page, i), params.columns[i].comment!, APP_NAMES.RECORDEDIT, true); + } + }); + + for await (const recordIndex of Array.from(Array(params.inputs.length).keys())) { + const formNumber = recordIndex + 1; + + for await (const col of params.columns) { + await test.step(`record ${formNumber}, column ${col.name} (${col.type}${col.arrayBaseType ? ' ' + col.arrayBaseType : ''}), `, async () => { + const existingValue = _getColumnValue(recordIndex, col.name); + const newValue = _getColumnInput(recordIndex, col.name); + const skipValidation = col.skipValidation || (!!existingValue && !newValue); + + // check the input and value + await test.step('show the input with the correct value', async () => { + await testInputValue(page, formNumber, col.name, col.displayname, col.type, !!col.disabled, existingValue); + }); + + if (col.disabled) return; + + // test the validators and extra features if needed + if (!skipValidation) { + let fkPopupTitle; + if (col.type === RecordeditInputType.FK_POPUP) { + fkPopupTitle = `Select ${col.displayname} for `; + if (isEditMode) { + fkPopupTitle += `${params.tableDisplayname}: `; + if (params.rowNames && params.rowNames[recordIndex]) { + fkPopupTitle += `${params.rowNames[recordIndex]}`; + } + } else { + fkPopupTitle += `new ${params.tableDisplayname}`; + } + } + await _testInputValidationAndExtraFeatures( + page, formNumber, col.name, col.displayname, col.type, col.arrayBaseType, existingValue, fkPopupTitle + ); + } + + // set the value + if (newValue === undefined) return; + await test.step('set the new value', async () => { + await setInputValue(page, formNumber, col.name, col.displayname, col.type, newValue, col.arrayBaseType); + }); + + }); + } + + } +} + + From a6609ccde03e20387e79a05154ec5f35fdbdaa53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:47:34 -0700 Subject: [PATCH 2/3] Bump webpack from 5.91.0 to 5.94.0 (#2529) Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index db00efcbe..b60ec5fb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2467,19 +2467,12 @@ "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.3", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -3040,10 +3033,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "peerDependencies": { "acorn": "^8" } @@ -4818,9 +4811,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -11681,20 +11674,19 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", From b4c1b0c0e3e2d56e097e9bc3d870f93a1b38b1bb Mon Sep 17 00:00:00 2001 From: Josh Chudy Date: Tue, 10 Sep 2024 13:54:54 -0700 Subject: [PATCH 3/3] migrated recordset add and edit specs (#2540) * migrated recordset add and edit specs * update text truncation tests to be multiple different steps so each part that fails is more accurate --- Makefile | 10 +- test/e2e/locators/recordset.ts | 8 + .../default-config/recordset/add.conf.js | 15 -- .../default-config/recordset/add.config.ts | 10 + .../default-config/recordset/add.spec.js | 188 ------------------ .../default-config/recordset/add.spec.ts | 130 ++++++++++++ .../default-config/recordset/edit.conf.js | 16 -- .../default-config/recordset/edit.config.ts | 10 + .../default-config/recordset/edit.spec.js | 135 ------------- .../default-config/recordset/edit.spec.ts | 34 ++++ test/e2e/utils/recordset-utils.ts | 24 +++ 11 files changed, 219 insertions(+), 361 deletions(-) delete mode 100644 test/e2e/specs/default-config/recordset/add.conf.js create mode 100644 test/e2e/specs/default-config/recordset/add.config.ts delete mode 100644 test/e2e/specs/default-config/recordset/add.spec.js create mode 100644 test/e2e/specs/default-config/recordset/add.spec.ts delete mode 100644 test/e2e/specs/default-config/recordset/edit.conf.js create mode 100644 test/e2e/specs/default-config/recordset/edit.config.ts delete mode 100644 test/e2e/specs/default-config/recordset/edit.spec.js create mode 100644 test/e2e/specs/default-config/recordset/edit.spec.ts diff --git a/Makefile b/Makefile index de8e6fc95..42b1d2ae5 100644 --- a/Makefile +++ b/Makefile @@ -63,8 +63,8 @@ E2EDrecordRelatedTable=test/e2e/specs/all-features/record/related-table.config.t E2EDrecordLinks=test/e2e/specs/default-config/record/links.config.ts # Recordset tests E2EDrecordset=test/e2e/specs/all-features-confirmation/recordset/presentation.config.ts -E2EDrecordsetEdit=test/e2e/specs/default-config/recordset/edit.conf.js -E2ErecordsetAdd=test/e2e/specs/default-config/recordset/add.conf.js +E2EDrecordsetEdit=test/e2e/specs/default-config/recordset/edit.config.ts +E2ErecordsetAdd=test/e2e/specs/default-config/recordset/add.config.ts E2EDrecordsetIndFacet=test/e2e/specs/delete-prohibited/recordset/facet.config.ts E2EDrecordsetHistFacet=test/e2e/specs/delete-prohibited/recordset/histogram-facet.config.ts E2ErecordsetSavedQuery=test/e2e/specs/all-features/recordset/saved-query.config.ts @@ -89,7 +89,6 @@ DefaultConfigParallel=test/e2e/specs/default-config/playwright.config.ts Manualrecordset=test/manual/specs/recordset.conf.js # protractor tests -RECORDSET_TESTS_PROTRACTOR=$(E2ErecordsetAdd) $(E2EDrecordsetEdit) RECORDADD_TESTS_PROTRACTOR=$(E2EDIrecordMultiFormInput) $(E2EDIrecordImmutable) RECORDEDIT_TESTS_PROTRACTOR=$(E2EDrecordEditSubmissionDisabled) DEFAULT_CONFIG_PARALLEL_TESTS_PROTRACTOR=$(DefaultConfigParallel_PROTRACTOR) @@ -99,7 +98,7 @@ ALL_TESTS_PROTRACTOR=$(RECORDSET_TESTS_PROTRACTOR) $(RECORDADD_TESTS_PROTRACTOR) # playwright tests NAVBAR_TESTS=$(E2Enavbar) $(E2EnavbarHeadTitle) $(E2EnavbarCatalogConfig) RECORD_TESTS=$(E2EDrecord) $(E2ErecordNoDeleteBtn) $(E2EDrecordRelatedTable) $(E2EDrecordCopy) $(E2EDrecordLinks) -RECORDSET_TESTS=$(E2EDrecordset) $(E2ErecordsetSavedQuery) $(E2EDrecordsetIndFacet) $(E2EDrecordsetHistFacet) +RECORDSET_TESTS=$(E2EDrecordset) $(E2ErecordsetAdd) $(E2EDrecordsetEdit) $(E2ErecordsetSavedQuery) $(E2EDrecordsetIndFacet) $(E2EDrecordsetHistFacet) RECORDADD_TESTS=$(E2EDIrecordAdd) $(E2EDIrecordMultiFormInput) $(E2ErecordEditForeignKeyDropdown) $(E2EDrecordEditCompositeKey) RECORDEDIT_TESTS=$(E2EDIrecordEdit) $(E2EDrecordEditNullValues) $(E2ErecordEditInputIframe) $(E2EDrecordEditDomainFilter) PERMISSIONS_TESTS=$(E2EmultiPermissionsVisibility) @@ -170,9 +169,6 @@ testpermissions:test-PERMISSIONS_TESTS .PHONY: testrecordset testrecordset: test-RECORDSET_TESTS -.PHONY: testrecordset-protractor -testrecordset-protractor: test_protractor-RECORDSET_TESTS_PROTRACTOR - #Rule to run the default chaise configuration tests in parallel .PHONY: testfooter testfooter: test-FOOTER_TESTS diff --git a/test/e2e/locators/recordset.ts b/test/e2e/locators/recordset.ts index 82f94a4dc..55818761d 100644 --- a/test/e2e/locators/recordset.ts +++ b/test/e2e/locators/recordset.ts @@ -59,6 +59,10 @@ export default class RecordsetLocators { return this.getPageTitleElement(container).locator('.chaise-icon-for-tooltip'); } + static getPageTitleInlineComment(container: Page | Locator): Locator { + return this.getPageTitleElement(container).locator('.inline-tooltip'); + }; + static getPermalinkButton(container: Page | Locator): Locator { return container.locator('#permalink'); } @@ -269,6 +273,10 @@ export default class RecordsetLocators { return RecordsetLocators.getRows(container).nth(rowIndex).locator('td').nth(0).locator('.select-action-button'); } + static getReadMore(container: Page | Locator): Locator { + return container.locator('.readmore'); + } + // ---------------- facet panel selectors ------------------ // diff --git a/test/e2e/specs/default-config/recordset/add.conf.js b/test/e2e/specs/default-config/recordset/add.conf.js deleted file mode 100644 index fd30fd36e..000000000 --- a/test/e2e/specs/default-config/recordset/add.conf.js +++ /dev/null @@ -1,15 +0,0 @@ -var pConfig = require('./../../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - configFileName: 'recordset/add.dev.json', - chaiseConfigFilePath: 'test/e2e/specs/default-config/chaise-config.js', - specs: [ - "add.spec.js" - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - } -}); - -exports.config = config; diff --git a/test/e2e/specs/default-config/recordset/add.config.ts b/test/e2e/specs/default-config/recordset/add.config.ts new file mode 100644 index 000000000..32e372833 --- /dev/null +++ b/test/e2e/specs/default-config/recordset/add.config.ts @@ -0,0 +1,10 @@ +import getConfig from '@isrd-isi-edu/chaise/test/e2e/setup/playwright.configuration'; + +export default getConfig({ + testName: 'default-config/recordset/add', + configFileName: 'recordset/add.dev.json', + mainSpecName: 'default-config', + testMatch: [ + 'add.spec.ts' + ] +}); diff --git a/test/e2e/specs/default-config/recordset/add.spec.js b/test/e2e/specs/default-config/recordset/add.spec.js deleted file mode 100644 index 89326ca2e..000000000 --- a/test/e2e/specs/default-config/recordset/add.spec.js +++ /dev/null @@ -1,188 +0,0 @@ -// TODO can be combined with recordset/edit.spec.js -var chaisePage = require('../../../utils/chaise.page.js'); -var moment = require('moment'); -var testParams = { - schemaName: "product-recordset-add", - table_name: "accommodation", - num_rows: 6, - title: "Best Western Plus Amedia Art Salzburg", - rating: "3.50", - summary: "The BEST WESTERN PLUS Amedia Art Salzburg is located near the traditional old part of town, near the highway, near the train station and close to the exhibition center of Salzburg.\nBEST WESTERN PLUS Amedia Art Salzburg offers harmony of modern technique and convenient atmosphere to our national and international business guest and tourists." -}; - -describe('Recordset add record,', function() { - - var allWindows;; - - beforeAll(function () { - chaisePage.navigate(browser.params.url + "/recordset/#" + browser.params.catalogId + "/" + testParams.schemaName + ":" + testParams.table_name); - chaisePage.recordsetPageReady(); - - // the truncation test sometimes would fail because it was running before rows show up - browser.wait(function () { - return chaisePage.recordsetPage.getRows().count().then(function (ct) { - return (ct > 0) - }); - }); - }); - - - it("show an inline comment instead of tooltip", function () { - expect(chaisePage.recordsetPage.getPageTitleInlineComment().getText()).toBe("Recordset inline comment", "inline comment is not shown or is incorrect"); - }); - - it("verify the text is truncated properly based on the default config, then not truncated after clicking 'more'", function (done) { - // default config: maxRecordsetRowHeight = 160 - // 160 for max height, 10 for padding - var testCell, cellHeight = 170; - chaisePage.recordsetPage.getRows().then(function (rows) { - return chaisePage.recordsetPage.getRowCells(rows[0]); - }).then(function (cells) { - testCell = cells[4]; - expect(testCell.getText()).toContain("... more"); - - return testCell.getSize(); - }).then(function (dimensions) { - // the calculations might be one pixel off - expect(Math.abs(dimensions.height - cellHeight) <= 1).toBeTruthy(); - return testCell.element(by.css(".readmore")).click(); - }).then(function () { - expect(testCell.getText()).toContain("... less"); - - return testCell.getSize(); - }).then(function (tallerDimensions) { - expect(tallerDimensions.height).toBeGreaterThan(cellHeight); - done(); - }).catch(function (err) { - console.log(err); - done.fail(err); - }); - }); - - it("verify view details link, search for a term, then verify view details link has changed", function (done) { - var baseUrl = '/record/#' + browser.params.catalogId + "/" + testParams.schemaName + ":" + testParams.table_name + "/RID="; - - chaisePage.recordsetPage.getRows().then(function (rows) { - var dataRow = browser.params.entities[testParams.schemaName][testParams.table_name].find(function (entity) { - return entity.id == 2003; - }); - // get first row view details button - expect(rows[0].element(by.css('.view-action-button')).getAttribute("href")).toContain(baseUrl + dataRow.RID, "View button url is incorrect before searching set"); - - // search for a row that is not the first one after sorting - chaisePage.recordsetPage.getMainSearchInput().sendKeys('hilton'); - return chaisePage.recordsetPage.getSearchSubmitButton().click(); - }).then(function() { - chaisePage.recordsetPage.waitForInverseMainSpinner(); - browser.wait(function () { - return chaisePage.recordsetPage.getRows().count().then(function (ct) { - return (ct == 1) - }); - }); - return chaisePage.recordsetPage.getRows(); - }).then(function(rows) { - expect(rows.length).toBe(1); - - var dataRow = browser.params.entities[testParams.schemaName][testParams.table_name].find(function (entity) { - return entity.id == 4004; - }); - // get first row view details button - expect(rows[0].element(by.css('.view-action-button')).getAttribute("href")).toContain(baseUrl + dataRow.RID, "View button url is incorrect after searching set"); - - // clear search - return chaisePage.recordsetPage.getSearchClearButton().click(); - }).then(function () { - done(); - }).catch(chaisePage.catchTestError(done)); - }); - - var allWindows; - it("click on the add button should open a new tab to recordedit", function(done) { - var rows; - var EC = protractor.ExpectedConditions; - var addRecordLink = chaisePage.recordsetPage.getAddRecordLink(); - browser.wait(EC.presenceOf(addRecordLink), browser.params.defaultTimeout); - - addRecordLink.click().then(function() { - return browser.getAllWindowHandles(); - }).then(function (handles) { - allWindows = handles; - return browser.switchTo().window(allWindows[1]); - }).then(function () { - chaisePage.waitForElement(element(by.id('submit-record-button'))); - return browser.driver.getCurrentUrl(); - }).then(function (url) { - - var recordeditUrl = '/recordedit/#' + browser.params.catalogId + "/" + testParams.schemaName + ":" + testParams.table_name; - expect(url).toContain(recordeditUrl, "url missmatch"); - - // set the required fields - return chaisePage.recordsetPage.getInputForAColumn("title"); - }).then(function(input) { - input.sendKeys(testParams.title); - - return chaisePage.recordsetPage.getModalPopupBtn('Category', 1); - }).then(function(btn) { - return btn.click(); - }).then(function() { - return chaisePage.recordsetPageReady(); - }).then(function() { - rows = chaisePage.recordsetPage.getRows(); - - return browser.wait(function () { - return rows.count().then(function (ct) { - return (ct == 5) - }); - }); - }).then(function () { - return rows.get(0).all(by.css(".select-action-button")); - }).then(function(selectButtons) { - selectButtons[0].click(); - }).then(function() { - return chaisePage.recordsetPage.getInputForAColumn("rating"); - }).then(function(input) { - input.sendKeys(testParams.rating); - return chaisePage.recordEditPage.getTextAreaForAColumn("summary"); - }).then(function(input) { - input.sendKeys(testParams.summary); - return chaisePage.recordEditPage.getTimestampInputsForAColumn('opened_on', 1).nowBtn.click(); - }).then (function () { - return chaisePage.recordEditPage.submitForm(); - }).then(function() { - // wait until redirected to record page - chaisePage.waitForElement(element(by.css(".record-main-section-table"))); - done(); - }).catch(function (err) { - done.fail(err); - }) - }); - - it("go back to recordset should refresh the table with the new record", function(done) { - // ... before closing this new tab and switching back to the original Record app's tab so that the next it spec can run properly - /** - * we noticed this test case started failing on saucelabs, - * that's why we're switching tabs twice to ensure the onfocus is getting called. - */ - browser.switchTo().window(allWindows[0]).then(function() { - return browser.switchTo().window(allWindows[1]); - }).then(function () { - return browser.switchTo().window(allWindows[0]); - }).then(function () { - return chaisePage.waitForElementInverse(element(by.id("spinner"))); - }).then(function() { - return chaisePage.recordsetPage.getPageTitleElement().click(); - }).then(function () { - return browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == testParams.num_rows+1); - }); - }, browser.params.defaultTimeout); - }).then(function () { - return chaisePage.recordsetPage.getRows(); - }).then(function(rows) { - expect(rows.length).toBe(testParams.num_rows+1); - done(); - }).catch(chaisePage.catchTestError(done)); - }) - -}); diff --git a/test/e2e/specs/default-config/recordset/add.spec.ts b/test/e2e/specs/default-config/recordset/add.spec.ts new file mode 100644 index 000000000..806a7245d --- /dev/null +++ b/test/e2e/specs/default-config/recordset/add.spec.ts @@ -0,0 +1,130 @@ +/* eslint-disable max-len */ +import { expect, test, Page } from '@playwright/test'; + +// locators +import RecordLocators from '@isrd-isi-edu/chaise/test/e2e/locators/record'; +import RecordeditLocators, { RecordeditInputType } from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; +import RecordsetLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordset'; + +// utils +import { getCatalogID, getEntityRow } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; +import { clickNewTabLink, manuallyTriggerFocus } from '@isrd-isi-edu/chaise/test/e2e/utils/page-utils'; +import { setInputValue } from '@isrd-isi-edu/chaise/test/e2e/utils/recordedit-utils'; + +const testParams = { + schema_name: 'product-recordset-add', + table_name: 'accommodation', + num_rows: 6, + title: 'Best Western Plus Amedia Art Salzburg', + rating: '3.50', + summary: 'The BEST WESTERN PLUS Amedia Art Salzburg is located near the traditional old part of town, near the highway, near the train station and close to the exhibition center of Salzburg.\nBEST WESTERN PLUS Amedia Art Salzburg offers harmony of modern technique and convenient atmosphere to our national and international business guest and tourists.' +}; + +test('Recordset add record', async ({ page, baseURL }, testInfo) => { + const PAGE_URL = `/recordset/#${getCatalogID(testInfo.project.name)}/${testParams.schema_name}:${testParams.table_name}`; + + let firstRow, testCell: any, dimensions; + await test.step('should load recordset page', async () => { + await page.goto(`${baseURL}${PAGE_URL}`); + await RecordsetLocators.waitForRecordsetPageReady(page); + + await expect.soft(RecordsetLocators.getRows(page)).toHaveCount(6); + }); + + await test.step('show an inline comment instead of tooltip', async () => { + await expect.soft(RecordsetLocators.getPageTitleInlineComment(page)).toHaveText('Recordset inline comment'); + }); + + await test.step('verify the text is truncated and "... more" is showing', async () => { + firstRow = RecordsetLocators.getRows(page).nth(0); + testCell = RecordsetLocators.getRowCells(firstRow).nth(4); + await expect.soft(testCell).toHaveText(/... more/); + }); + + // default config: maxRecordsetRowHeight = 160 + // 160 for max height, 10 for padding + const cellHeight = 170; + await test.step('the cell with text has the expected height when truncated', async () => { + dimensions = await testCell.boundingBox(); + expect.soft(dimensions).toBeDefined(); + + if (dimensions) { + // the calculations might be one pixel off + expect.soft(Math.abs(dimensions.height - cellHeight)).toBeLessThanOrEqual(1); + } + }); + + await test.step('click "... more" and verify "... less" shows up', async () => { + await RecordsetLocators.getReadMore(testCell).click(); + await expect.soft(testCell).toHaveText(/... less/); + }); + + await test.step('the cell with text is expanded with a new height', async () => { + dimensions = await testCell.boundingBox(); + expect.soft(dimensions).toBeDefined(); + + if (dimensions) { + expect.soft(dimensions.height).toBeGreaterThan(cellHeight); + }; + }); + + await test.step('click on "... less" and verify the content is truncated again', async() => { + await RecordsetLocators.getReadMore(testCell).click(); + await expect.soft(testCell).toHaveText(/... more/); + }); + + await test.step('verify view details link, search for a term, then verify view details link has changed', async () => { + const baseViewUrl = `${PAGE_URL.replace('recordset', 'record')}/RID=`; + + let keyValues = [{ column: 'id', value: '2003' }]; + let rowRID = getEntityRow(testInfo, testParams.schema_name, testParams.table_name, keyValues).RID; + // get first row view details button + expect.soft(await RecordsetLocators.getRowViewButton(page, 0).getAttribute('href')).toContain(`${baseURL}${baseViewUrl}${rowRID}`); + + // search for a row that is not the first one after sorting + await RecordsetLocators.getMainSearchInput(page).fill('hilton'); + await RecordsetLocators.getSearchSubmitButton(page).click(); + + await RecordsetLocators.waitForRecordsetPageReady(page); + await expect.soft(RecordsetLocators.getRows(page)).toHaveCount(1); + + keyValues = [{ column: 'id', value: '4004' }]; + rowRID = getEntityRow(testInfo, testParams.schema_name, testParams.table_name, keyValues).RID; + // get first row view details button + expect.soft(await RecordsetLocators.getRowViewButton(page, 0).getAttribute('href')).toContain(`${baseURL}${baseViewUrl}${rowRID}`); + + // clear search + await RecordsetLocators.getSearchClearButton(page).click(); + }); + + let newPage: Page; + await test.step('click on the add button should open a new tab to recordedit', async () => { + newPage = await clickNewTabLink(RecordsetLocators.getAddRecordsLink(page)); + + await RecordeditLocators.waitForRecordeditPageReady(newPage); + + + const recordeditUrl = PAGE_URL.replace('recordset', 'recordedit'); + expect.soft(newPage.url()).toContain(recordeditUrl); + + // set the required fields + await setInputValue(newPage, 1, 'title', 'Title', RecordeditInputType.TEXT, testParams.title); + await setInputValue(newPage, 1, 'category', 'Category', RecordeditInputType.FK_POPUP, {modal_num_rows: 5, modal_option_index: 0}); + await setInputValue(newPage, 1, 'rating', 'Rating', RecordeditInputType.TEXT, testParams.rating); + await setInputValue(newPage, 1, 'summary', 'Summary', RecordeditInputType.LONGTEXT, testParams.summary); + await RecordeditLocators.getTimestampInputsForAColumn(newPage, 'opened_on', 1).nowBtn.click(); + + await RecordeditLocators.submitForm(newPage); + // wait until redirected to record page + await RecordLocators.waitForRecordPageReady(newPage); + }); + + await test.step('go back to recordset should refresh the table with the new record', async () => { + if (!newPage) return; + + await newPage.close(); + await manuallyTriggerFocus(page); + + await expect.soft(RecordsetLocators.getRows(page)).toHaveCount(testParams.num_rows+1); + }); +}); diff --git a/test/e2e/specs/default-config/recordset/edit.conf.js b/test/e2e/specs/default-config/recordset/edit.conf.js deleted file mode 100644 index 371a1d987..000000000 --- a/test/e2e/specs/default-config/recordset/edit.conf.js +++ /dev/null @@ -1,16 +0,0 @@ -var pConfig = require('./../../../utils/protractor.configuration.js'); - -var config = pConfig.getConfig({ - configFileName: 'recordset/edit.dev.json', - chaiseConfigFilePath: 'test/e2e/specs/default-config/chaise-config.js', - specs: [ - "edit.spec.js" - ], - setBaseUrl: function(browser, data) { - browser.params.url = process.env.CHAISE_BASE_URL; - return browser.params.url; - } - -}); - -exports.config = config; diff --git a/test/e2e/specs/default-config/recordset/edit.config.ts b/test/e2e/specs/default-config/recordset/edit.config.ts new file mode 100644 index 000000000..0ddccfabc --- /dev/null +++ b/test/e2e/specs/default-config/recordset/edit.config.ts @@ -0,0 +1,10 @@ +import getConfig from '@isrd-isi-edu/chaise/test/e2e/setup/playwright.configuration'; + +export default getConfig({ + testName: 'default-config/recordset/edit', + configFileName: 'recordset/edit.dev.json', + mainSpecName: 'default-config', + testMatch: [ + 'edit.spec.ts' + ] +}); diff --git a/test/e2e/specs/default-config/recordset/edit.spec.js b/test/e2e/specs/default-config/recordset/edit.spec.js deleted file mode 100644 index 9cf9e4591..000000000 --- a/test/e2e/specs/default-config/recordset/edit.spec.js +++ /dev/null @@ -1,135 +0,0 @@ -var chaisePage = require('../../../utils/chaise.page.js'); -var testParams = { - schemaName: "recordset-multi-edit", - table_name: "multi-edit-table", - default_page_limit: 25, - limit: 10, - int_23_count: 11 -}; - -describe('Recordset edit records,', function() { - - describe("recordset shows results with no limit defined,", function() { - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordset/#" + browser.params.catalogId + "/" + testParams.schemaName + ":" + testParams.table_name); - - chaisePage.recordsetPageReady() - }); - - it("clicking edit will show forms based on the default page size of " + testParams.default_page_limit + ".", function() { - browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == testParams.default_page_limit); - }); - }, browser.params.defaultTimeout); - - chaisePage.recordsetPage.getRows().count().then(function(ct) { - expect(ct).toBe(testParams.default_page_limit); - - return chaisePage.recordsetPage.getEditRecordLink().click(); - }).then(function() { - browser.wait(function() { - return chaisePage.recordEditPage.getRecordeditForms().count().then(function(ct) { - return (ct == testParams.default_page_limit); - }); - }, browser.params.defaultTimeout); - - return chaisePage.recordEditPage.getRecordeditForms().count(); - }).then(function(count) { - expect(count).toBe(testParams.default_page_limit); - }); - }); - }); - - describe("recordset url includes a limit,", function() { - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordset/#" + browser.params.catalogId + "/" + testParams.schemaName + ":" + testParams.table_name + "?limit=" + testParams.limit); - - chaisePage.recordsetPageReady() - }); - - it("clicking edit will show forms based on the limit of " + testParams.limit + " in the uri.", function() { - browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == testParams.limit); - }); - }, browser.params.defaultTimeout); - - chaisePage.recordsetPage.getRows().count().then(function(ct) { - expect(ct).toBe(testParams.limit); - - return chaisePage.recordsetPage.getEditRecordLink().click(); - }).then(function() { - browser.wait(function() { - return chaisePage.recordEditPage.getRecordeditForms().count().then(function(ct) { - return (ct == testParams.limit); - }); - }, browser.params.defaultTimeout); - - return chaisePage.recordEditPage.getRecordeditForms().count(); - }).then(function(count) { - expect(count).toBe(testParams.limit); - }); - }); - }); - - describe("recordset url includes a filter of int=23", function() { - beforeAll(function() { - chaisePage.navigate(browser.params.url + "/recordset/#" + browser.params.catalogId + "/" + testParams.schemaName + ":" + testParams.table_name + "/int=23"); - - chaisePage.recordsetPageReady() - }); - - it("without a limit, clicking edit will show all forms with int=23.", function() { - browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == testParams.int_23_count); - }); - }, browser.params.defaultTimeout); - - chaisePage.recordsetPage.getRows().count().then(function(ct) { - expect(ct).toBe(testParams.int_23_count); - - return chaisePage.recordsetPage.getEditRecordLink().click(); - }).then(function() { - browser.wait(function() { - return chaisePage.recordEditPage.getRecordeditForms().count().then(function(ct) { - return (ct == testParams.int_23_count); - }); - }, browser.params.defaultTimeout); - - return chaisePage.recordEditPage.getRecordeditForms().count(); - }).then(function(count) { - expect(count).toBe(testParams.int_23_count); - }); - }); - - it("with a limit of " + testParams.limit + ", clicking edit will show all forms with int=23.", function() { - chaisePage.navigate(browser.params.url + "/recordset/#" + browser.params.catalogId + "/" + testParams.schemaName + ":" + testParams.table_name + "/int=23?limit=" + testParams.limit); - - chaisePage.recordsetPageReady() - - browser.wait(function() { - return chaisePage.recordsetPage.getRows().count().then(function(ct) { - return (ct == testParams.limit); - }); - }, browser.params.defaultTimeout); - - chaisePage.recordsetPage.getRows().count().then(function(ct) { - expect(ct).toBe(testParams.limit); - - return chaisePage.recordsetPage.getEditRecordLink().click(); - }).then(function() { - browser.wait(function() { - return chaisePage.recordEditPage.getRecordeditForms().count().then(function(ct) { - return (ct == testParams.limit); - }); - }, browser.params.defaultTimeout); - - return chaisePage.recordEditPage.getRecordeditForms().count(); - }).then(function(count) { - expect(count).toBe(testParams.limit); - }); - }); - }); -}); diff --git a/test/e2e/specs/default-config/recordset/edit.spec.ts b/test/e2e/specs/default-config/recordset/edit.spec.ts new file mode 100644 index 000000000..8132644d7 --- /dev/null +++ b/test/e2e/specs/default-config/recordset/edit.spec.ts @@ -0,0 +1,34 @@ +import { test } from '@playwright/test'; + +// utils +import { getCatalogID } from '@isrd-isi-edu/chaise/test/e2e/utils/catalog-utils'; +import { testBulkEditLink } from '@isrd-isi-edu/chaise/test/e2e/utils/recordset-utils'; + +const testParams = { + schema_name: 'recordset-multi-edit', + table_name: 'multi-edit-table', + default_page_limit: 25, + limit: 10, + int_23_count: 11 +}; + +test.describe('Recordset edit records', async () => { + test.describe.configure({ mode: 'parallel' }); + + const PAGE_URL = (projectName: string) => `/recordset/#${getCatalogID(projectName)}/${testParams.schema_name}:${testParams.table_name}` + test('recordset shows results with no limit defined', async ({ page, baseURL }, testInfo) => { + await testBulkEditLink(page, `${baseURL}${PAGE_URL(testInfo.project.name)}`, testParams.default_page_limit); + }); + + test('recordset url includes a limit', async ({ page, baseURL }, testInfo) => { + await testBulkEditLink(page, `${baseURL}${PAGE_URL(testInfo.project.name)}?limit=${testParams.limit}`, testParams.limit); + }); + + test('recordset url includes a filter of int=23 without a limit', async ({ page, baseURL }, testInfo) => { + await testBulkEditLink(page, `${baseURL}${PAGE_URL(testInfo.project.name)}/int=23`, testParams.int_23_count); + }); + + test(`recordset url includes a filter of int=23 with a limit of ${testParams.limit}`, async ({ page, baseURL }, testInfo) => { + await testBulkEditLink(page, `${baseURL}${PAGE_URL(testInfo.project.name)}/int=23?limit=${testParams.limit}`, testParams.limit); + }); +}); diff --git a/test/e2e/utils/recordset-utils.ts b/test/e2e/utils/recordset-utils.ts index 3652a053d..798e4a138 100644 --- a/test/e2e/utils/recordset-utils.ts +++ b/test/e2e/utils/recordset-utils.ts @@ -2,10 +2,13 @@ import { expect, Locator, Page, test } from '@playwright/test' // locators import ModalLocators from '@isrd-isi-edu/chaise/test/e2e/locators/modal'; +import RecordeditLocators from '@isrd-isi-edu/chaise/test/e2e/locators/recordedit'; import RecordsetLocators, { DefaultRangeInputLocators, TimestampRangeInputLocators } from '@isrd-isi-edu/chaise/test/e2e/locators/recordset'; + +// utils import { Either } from '@isrd-isi-edu/chaise/src/utils/type-utils'; type RecordsetColStringValue = { @@ -995,3 +998,24 @@ export async function testIndividualFacet(page: Page, pageSize: number, totalNum break; } } + +/** + * test navigating to recordset and clicking the bulk edit link to ensure the same number of rows are shown in both apps + * + * @param url recordset url to navigate to for this test + * @param count the count of recordset rows and recordedit forms + */ +export async function testBulkEditLink(page: Page, url: string, count: number) { + await test.step('should load recordset page', async () => { + await page.goto(`${url}`); + await RecordsetLocators.waitForRecordsetPageReady(page); + }); + + await test.step(`clicking edit will show ${count} forms.`, async () => { + await expect.soft(RecordsetLocators.getRows(page)).toHaveCount(count); + await RecordsetLocators.getBulkEditLink(page).click(); + + await RecordeditLocators.waitForRecordeditPageReady(page); + await expect.soft(RecordeditLocators.getRecordeditForms(page)).toHaveCount(count); + }); +}