Skip to content

Conversation

@asirvadAbrahamVarghese
Copy link
Contributor

CP4AIOPS-18932:
Add test to cover:
 - Validate form elements
 - Validate reset and apply functionalities
 
@miq-bot add-label cypress
@miq-bot add-label test
@miq-bot assign @jrafanie

@asirvadAbrahamVarghese asirvadAbrahamVarghese requested a review from a team as a code owner October 28, 2025 10:43
@asirvadAbrahamVarghese asirvadAbrahamVarghese force-pushed the service-requests-form-automation-testing branch 2 times, most recently from 0ac63bc to 20b1181 Compare October 28, 2025 10:45
@asirvadAbrahamVarghese
Copy link
Contributor Author

asirvadAbrahamVarghese commented Oct 28, 2025

TODO:

Add changes from
#9689
#9690
#9691

@asirvadAbrahamVarghese asirvadAbrahamVarghese force-pushed the service-requests-form-automation-testing branch 4 times, most recently from b7b7b84 to 4cf95df Compare October 29, 2025 11:30
@jrafanie jrafanie closed this Oct 29, 2025
@jrafanie jrafanie reopened this Oct 29, 2025
@asirvadAbrahamVarghese asirvadAbrahamVarghese force-pushed the service-requests-form-automation-testing branch 2 times, most recently from fc56d7a to e38467e Compare October 30, 2025 05:06
@asirvadAbrahamVarghese asirvadAbrahamVarghese changed the title Added automated tests with cypress for Service Requests form [WIP] - Added automated tests with cypress for Service Requests form Oct 31, 2025
@asirvadAbrahamVarghese asirvadAbrahamVarghese force-pushed the service-requests-form-automation-testing branch from e38467e to c4e6f6f Compare November 5, 2025 09:41
AUTOMATION_MENU_OPTION,
EMBEDDED_AUTOMATION_MENU_OPTION,
CUSTOMIZATION_MENU_OPTION
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I posted this in chat but wanted to share it here...

For when you do factories...

One thing I found helpful is leave the existing setup and teardown code in your test... add .only to the cypress test it.only('... so it runs just that test, and clear or delete your development.log. Then, run the test with just the setup and teardown code and look at the development log... I noticed the UI creation of service templates also create resource actions so I also added them to the factories used by cypress even though the test passes with them: https://github.com/ManageIQ/manageiq-ui-classic/pull/9699/files#diff-2cf6fcbdf170cc059506136bd679f8cbbe116eaa979d06281d97c060b2a7ef33R119-R120

If you need help to review the logs, let me know. Generally, you can grep INSERT" log/development.log and find where it's creating the the objects you clicked in the UI. You'll need to find the factory in https://github.com/ManageIQ/manageiq/tree/master/spec/factories. Follow the examples I've done previously but certainly reach out if you get stuck.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, if you have all the INSERT statements issued in the test during the setup where the test is driving the UI to create the objects, we should be able to convert things to factories to replace it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll run this test locally and show you what I get

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

joerafaniello@MacBookPro manageiq % grep -E "Started|Completed|INSERT" log/development.log


[----] I, [2025-11-05T11:58:30.647858#71052:efab0]  INFO -- development: Started POST "/dashboard/authenticate?button=login" for 127.0.0.1 at 2025-11-05 11:58:30 -0500
[----] D, [2025-11-05T11:58:30.760566#71052:efab0] DEBUG -- development:   AuditEvent Create (1.6ms)  INSERT INTO "audit_events" ("event", "status", "message", "severity", "userid", "source", "created_on") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["event", "authenticate_database"], ["status", "success"], ["message", "User admin successfully validated by EVM"], ["severity", "info"], ["userid", "admin"], ["source", "Base.audit_success"], ["created_on", "2025-11-05 16:58:30.756921"]]
[----] D, [2025-11-05T11:58:30.768493#71052:efab0] DEBUG -- development:   AuditEvent Create (1.5ms)  INSERT INTO "audit_events" ("event", "status", "message", "severity", "userid", "source", "created_on") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["event", "authenticate_database"], ["status", "success"], ["message", "Authentication successful for user admin"], ["severity", "info"], ["userid", "admin"], ["source", "Base.audit_success"], ["created_on", "2025-11-05 16:58:30.766361"]]
[----] D, [2025-11-05T11:58:30.803027#71052:efab0] DEBUG -- development:   Session Create (0.8ms)  INSERT INTO "sessions" ("session_id", "updated_at") VALUES ($1, $2) RETURNING "id"  [["session_id", "2::524ef919897277ca79a53b3ac8ade99d4e3992c2ddfbce044115e751a4b00d37"], ["updated_at", "2025-11-05 16:58:30.801972"]]
[----] I, [2025-11-05T11:58:30.989320#71052:efab0]  INFO -- development: Completed 200 OK in 338ms (Views: 0.5ms | ActiveRecord: 164.0ms (19 queries, 0 cached) | GC: 5.7ms)
^^^ No need to do this, login creates this stuff, unrelated to your test setup.
...
[----] I, [2025-11-05T11:58:34.187010#71052:f7bfc]  INFO -- development: Started POST "/api/service_dialogs" for 127.0.0.1 at 2025-11-05 11:58:34 -0500
[----] D, [2025-11-05T11:58:34.299928#71052:f7bfc] DEBUG -- development:   DialogFieldTextBox Create (4.3ms)  INSERT INTO "dialog_fields" ("name", "description", "type", "display", "required", "default_value", "options", "created_at", "updated_at", "label", "position", "validator_type", "reconfigurable", "dynamic", "show_refresh_button", "load_values_on_init", "read_only", "auto_refresh", "trigger_auto_refresh", "visible") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) RETURNING "id"  [["name", "text_box_1"], ["description", ""], ["type", "DialogFieldTextBox"], ["display", "edit"], ["required", false], ["default_value", ""], ["options", "---\n:protected: false\n"], ["created_at", "2025-11-05 16:58:34.284263"], ["updated_at", "2025-11-05 16:58:34.284263"], ["label", "Text Box"], ["position", 0], ["validator_type", "f"], ["reconfigurable", false], ["dynamic", false], ["show_refresh_button", false], ["load_values_on_init", true], ["read_only", false], ["auto_refresh", false], ["trigger_auto_refresh", false], ["visible", true]]
[----] D, [2025-11-05T11:58:34.316538#71052:f7bfc] DEBUG -- development:   ResourceAction Create (0.9ms)  INSERT INTO "resource_actions" ("resource_id", "resource_type", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["resource_id", 1], ["resource_type", "DialogField"], ["created_at", "2025-11-05 16:58:34.314120"], ["updated_at", "2025-11-05 16:58:34.314120"]]
[----] D, [2025-11-05T11:58:34.340159#71052:f7bfc] DEBUG -- development:   DialogGroup Create (1.3ms)  INSERT INTO "dialog_groups" ("created_at", "updated_at", "label", "position") VALUES ($1, $2, $3, $4) RETURNING "id"  [["created_at", "2025-11-05 16:58:34.337602"], ["updated_at", "2025-11-05 16:58:34.337602"], ["label", "New section"], ["position", 0]]
[----] D, [2025-11-05T11:58:34.348996#71052:f7bfc] DEBUG -- development:   DialogTab Create (1.4ms)  INSERT INTO "dialog_tabs" ("created_at", "updated_at", "label", "position") VALUES ($1, $2, $3, $4) RETURNING "id"  [["created_at", "2025-11-05 16:58:34.346460"], ["updated_at", "2025-11-05 16:58:34.346460"], ["label", "New tab"], ["position", 0]]
[----] D, [2025-11-05T11:58:34.354299#71052:f7bfc] DEBUG -- development:   Dialog Create (1.0ms)  INSERT INTO "dialogs" ("buttons", "created_at", "updated_at", "label", "system") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["buttons", "submit,cancel"], ["created_at", "2025-11-05 16:58:34.352145"], ["updated_at", "2025-11-05 16:58:34.352145"], ["label", "Test-Dialog-Name"], ["system", false]]
[----] I, [2025-11-05T11:58:34.373451#71052:f7bfc]  INFO -- development: Completed 200 OK in 180ms (Views: 0.1ms | ActiveRecord: 75.5ms (18 queries, 0 cached) | GC: 0.0ms)
^^^ dialog creation
...
[----] I, [2025-11-05T11:58:38.640171#71052:efab0]  INFO -- development: Started POST "/catalog/servicetemplate_edit?button=add" for 127.0.0.1 at 2025-11-05 11:58:38 -0500
[----] D, [2025-11-05T11:58:38.824847#71052:efab0] DEBUG -- development:   ServiceTemplate Create (1.1ms)  INSERT INTO "service_templates" ("name", "created_at", "updated_at", "display", "miq_group_id", "prov_type", "service_template_catalog_id", "tenant_id", "generic_subtype", "guid", "internal", "service_type") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id"  [["name", "Test-Catalog-Item"], ["created_at", "2025-11-05 16:58:38.822487"], ["updated_at", "2025-11-05 16:58:38.822487"], ["display", true], ["miq_group_id", 2], ["prov_type", "generic"], ["service_template_catalog_id", 1], ["tenant_id", 1], ["generic_subtype", "custom"], ["guid", "46e5397a-96df-49ea-829c-18af341383f5"], ["internal", false], ["service_type", "atomic"]]
[----] D, [2025-11-05T11:58:38.836731#71052:efab0] DEBUG -- development:   ResourceAction Create (0.7ms)  INSERT INTO "resource_actions" ("action", "dialog_id", "resource_id", "resource_type", "created_at", "updated_at", "ae_namespace", "ae_class", "ae_instance", "ae_attributes") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING "id"  [["action", "Provision"], ["dialog_id", 1], ["resource_id", 283], ["resource_type", "ServiceTemplate"], ["created_at", "2025-11-05 16:58:38.835187"], ["updated_at", "2025-11-05 16:58:38.835187"], ["ae_namespace", "Service/Provisioning/StateMachines"], ["ae_class", "ServiceProvision_Template"], ["ae_instance", "CatalogItemInitialization"], ["ae_attributes", "---\n:service_action: Provision\n"]]
[----] D, [2025-11-05T11:58:38.841927#71052:efab0] DEBUG -- development:   ResourceAction Create (0.5ms)  INSERT INTO "resource_actions" ("action", "dialog_id", "resource_id", "resource_type", "created_at", "updated_at", "ae_namespace", "ae_class", "ae_instance", "ae_attributes") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING "id"  [["action", "Retirement"], ["dialog_id", 1], ["resource_id", 283], ["resource_type", "ServiceTemplate"], ["created_at", "2025-11-05 16:58:38.840852"], ["updated_at", "2025-11-05 16:58:38.840852"], ["ae_namespace", "Service/Retirement/StateMachines"], ["ae_class", "ServiceRetirement"], ["ae_instance", "Default"], ["ae_attributes", "---\n:service_action: Retirement\n"]]
[----] I, [2025-11-05T11:58:38.868845#71052:efab0]  INFO -- development: Completed 200 OK in 226ms (Views: 1.0ms | ActiveRecord: 81.0ms (278 queries, 41 cached) | GC: 1.4ms)
^ service template creation, should be similar to mine: https://github.com/ManageIQ/manageiq-ui-classic/pull/9699
...
[----] D, [2025-11-05T11:58:40.657471#71052:d598] DEBUG -- development:   ServiceTemplateProvisionRequest Create (6.8ms)  INSERT INTO "miq_requests" ("approval_state", "type", "created_on", "updated_on", "requester_id", "requester_name", "request_type", "options", "userid", "tenant_id", "initiated_by", "message", "request_state", "status", "process", "source_id", "source_type") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING "id"  [["approval_state", "pending_approval"], ["type", "ServiceTemplateProvisionRequest"], ["created_on", "2025-11-05 16:58:40.648277"], ["updated_on", "2025-11-05 16:58:40.648277"], ["requester_id", 1], ["requester_name", "Administrator"], ["request_type", "clone_to_service"], ["options", "---\n:dialog:\n  dialog_text_box_1: ''\n:workflow_settings:\n  :resource_action_id: 555\n  :dialog_id: 1\n:initiator:\n:src_id: 283\n:request_options:\n  :submit_workflow: true\n  :init_defaults: false\n:cart_state: ordered\n:requester_group: EvmGroup-super_administrator\n"], ["userid", "admin"], ["tenant_id", 1], ["initiated_by", "user"], ["message", "Service_Template_Provisioning - Request Created"], ["request_state", "pending"], ["status", "Ok"], ["process", false], ["source_id", 283], ["source_type", "ServiceTemplate"]]
[----] D, [2025-11-05T11:58:40.662003#71052:d598] DEBUG -- development:   MiqApproval Create (2.4ms)  INSERT INTO "miq_approvals" ("description", "miq_request_id", "created_on", "updated_on", "state") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["description", "Default Approval"], ["miq_request_id", 1], ["created_on", "2025-11-05 16:58:40.657865"], ["updated_on", "2025-11-05 16:58:40.657865"], ["state", "pending"]]
[----] D, [2025-11-05T11:58:40.686587#71052:d598] DEBUG -- development:   ServiceOrderCart Create (1.5ms)  INSERT INTO "service_orders" ("tenant_id", "user_id", "user_name", "state", "created_at", "updated_at", "type") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["tenant_id", 1], ["user_id", 1], ["user_name", "Administrator"], ["state", "ordered"], ["created_at", "2025-11-05 16:58:40.683408"], ["updated_at", "2025-11-05 16:58:40.683408"], ["type", "ServiceOrderCart"]]
[----] D, [2025-11-05T11:58:40.716376#71052:d598] DEBUG -- development:   MiqQueue Create (1.4ms)  INSERT INTO "miq_queue" ("priority", "method_name", "state", "created_on", "updated_on", "queue_name", "class_name", "instance_id", "args", "msg_timeout", "lock_version") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING "id"  [["priority", 100], ["method_name", "call_automate_event"], ["state", "ready"], ["created_on", "2025-11-05 16:58:40.711204"], ["updated_on", "2025-11-05 16:58:40.711204"], ["queue_name", "generic"], ["class_name", "ServiceTemplateProvisionRequest"], ["instance_id", 1], ["args", "---\n- request_created\n"], ["msg_timeout", 3600], ["lock_version", 0]]
[----] D, [2025-11-05T11:58:40.724547#71052:d598] DEBUG -- development:   AuditEvent Create (1.3ms)  INSERT INTO "audit_events" ("event", "status", "message", "severity", "target_class", "userid", "source", "created_on") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "id"  [["event", "service_template_provision_request_created"], ["status", "success"], ["message", "Service_Template_Provisioning requested by <admin> for ServiceTemplate:283"], ["severity", "info"], ["target_class", "ServiceTemplate"], ["userid", "admin"], ["source", "MiqRequest.audit_request_success"], ["created_on", "2025-11-05 16:58:40.722632"]]
[----] I, [2025-11-05T11:58:40.734359#71052:d598]  INFO -- development: Completed 200 OK in 179ms (Views: 0.3ms | ActiveRecord: 71.9ms (32 queries, 1 cached) | GC: 1.6ms)
^^^ Request creation.  I'm not sure if you need to create the queue item as that might occur just from creating the service order and ordering it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, I ran it with this change:

index eb26c39014..727df360c7 100644
--- a/cypress/e2e/ui/Services/Requests/service_requests.cy.js
+++ b/cypress/e2e/ui/Services/Requests/service_requests.cy.js
@@ -256,175 +256,7 @@ describe('Automate Service Requests form operations: Services > Requests', () =>
       dataSetup();
     });
 
-    it('Validate reset & apply buttons', () => {
-      /* Reset */
-      cy.getFormSelectFieldById({ selectId: 'selectedUser' }).select(
-        'Administrator'
-      );
-      cy.getFormLabelByForAttribute({
-        forValue: 'approvalStates-pending_approval',
-      }).click();
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-pending_approval',
-        inputType: 'checkbox',
-      }).should('not.be.checked');
-      cy.getFormLabelByForAttribute({
-        forValue: 'approvalStates-approved',
-      }).click();
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-approved',
-        inputType: 'checkbox',
-      }).should('not.be.checked');
-      cy.getFormLabelByForAttribute({
-        forValue: 'approvalStates-denied',
-      }).click();
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-denied',
-        inputType: 'checkbox',
-      }).should('not.be.checked');
-      cy.getFormSelectFieldById({ selectId: 'types' }).select(
-        TYPE_VM_PROVISION
-      );
-      cy.getFormSelectFieldById({ selectId: 'selectedPeriod' }).select(
-        'Last 30 Days'
-      );
-      cy.getFormInputFieldByIdAndType({ inputId: 'reasonText' }).type(
-        'Testing'
-      );
-      // TODO: Replace with getFormFooterButtonByTypeWithText once #9689 is merged
-      cy.contains(
-        `#main-content button[type="button"]`,
-        RESET_BUTTON_TEXT
-      ).click();
-      // cy.getFormFooterButtonByTypeWithText({
-      //   buttonText: RESET_BUTTON_TEXT,
-      //   buttonWrapperClass: 'custom-button-wrapper',
-      // }).click();
-      cy.getFormSelectFieldById({ selectId: 'selectedUser' }).should(
-        'have.value',
-        SELECT_OPTION_ALL
-      );
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-pending_approval',
-        inputType: 'checkbox',
-      }).should('be.checked');
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-approved',
-        inputType: 'checkbox',
-      }).should('be.checked');
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-denied',
-        inputType: 'checkbox',
-      }).should('be.checked');
-      cy.getFormSelectFieldById({ selectId: 'types' }).should(
-        'have.value',
-        SELECT_OPTION_ALL
-      );
-      cy.getFormSelectFieldById({ selectId: 'selectedPeriod' }).should(
-        'have.value',
-        REQUEST_DATE_LAST_7_DAYS
-      );
-      cy.getFormInputFieldByIdAndType({ inputId: 'reasonText' }).should(
-        'have.value',
-        ''
-      );
-      /* Apply */
-      // Filter data with approval state
-      cy.getFormLabelByForAttribute({
-        forValue: 'approvalStates-pending_approval',
-      }).click();
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-pending_approval',
-        inputType: 'checkbox',
-      }).should('not.be.checked');
-      cy.getFormLabelByForAttribute({
-        forValue: 'approvalStates-approved',
-      }).click();
-      cy.getFormInputFieldByIdAndType({
-        inputId: 'approvalStates-approved',
-        inputType: 'checkbox',
-      }).should('not.be.checked');
-      // TODO: Replace with getFormFooterButtonByTypeWithText once #9689 is merged
-      cy.contains(
-        `#main-content button[type="submit"]`,
-        APPLY_BUTTON_TEXT
-      ).click();
-      // cy.getFormFooterButtonByTypeWithText({
-      //   buttonText: APPLY_BUTTON_TEXT,
-      //   buttonWrapperClass: 'custom-button-wrapper',
-      //   buttonType: 'submit',
-      // }).click();
-      // TODO: Replace with verify_gtl_no_records_text once #9691 is merged
-      cy.contains('#miq-gtl-view .no-records-found', 'No records found');
-      // cy.verify_gtl_no_records_text();
-      // TODO: Replace with getFormFooterButtonByTypeWithText once #9689 is merged
-      cy.contains(
-        `#main-content button[type="button"]`,
-        RESET_BUTTON_TEXT
-      ).click();
-      // cy.getFormFooterButtonByTypeWithText({
-      //   buttonText: RESET_BUTTON_TEXT,
-      //   buttonWrapperClass: 'custom-button-wrapper',
-      // }).click();
-      cy.gtlGetRows([0]).then((data) => {
-        expect(data.length).to.equal(1);
-      });
-      // Filter data with type
-      cy.getFormSelectFieldById({ selectId: 'types' }).select(
-        TYPE_VM_PROVISION
-      );
-      // TODO: Replace with getFormFooterButtonByTypeWithText once #9689 is merged
-      cy.contains(
-        `#main-content button[type="submit"]`,
-        APPLY_BUTTON_TEXT
-      ).click();
-      // cy.getFormFooterButtonByTypeWithText({
-      //   buttonText: APPLY_BUTTON_TEXT,
-      //   buttonWrapperClass: 'custom-button-wrapper',
-      //   buttonType: 'submit',
-      // }).click();
-      // TODO: Replace with verify_gtl_no_records_text once #9691 is merged
-      cy.contains('#miq-gtl-view .no-records-found', 'No records found');
-      // cy.verify_gtl_no_records_text();
-      // TODO: Replace with getFormFooterButtonByTypeWithText once #9689 is merged
-      cy.contains(
-        `#main-content button[type="button"]`,
-        RESET_BUTTON_TEXT
-      ).click();
-      // cy.getFormFooterButtonByTypeWithText({
-      //   buttonText: RESET_BUTTON_TEXT,
-      //   buttonWrapperClass: 'custom-button-wrapper',
-      // }).click();
-      cy.gtlGetRows([0]).then((data) => {
-        expect(data.length).to.equal(1);
-      });
-      // Filter data with
-      cy.getFormInputFieldByIdAndType({ inputId: 'reasonText' }).type('r@ndOm');
-      // TODO: Replace with getFormFooterButtonByTypeWithText once #9689 is merged
-      cy.contains(
-        `#main-content button[type="submit"]`,
-        APPLY_BUTTON_TEXT
-      ).click();
-      // cy.getFormFooterButtonByTypeWithText({
-      //   buttonText: APPLY_BUTTON_TEXT,
-      //   buttonWrapperClass: 'custom-button-wrapper',
-      //   buttonType: 'submit',
-      // }).click();
-      // TODO: Replace with verify_gtl_no_records_text once #9691 is merged
-      cy.contains('#miq-gtl-view .no-records-found', 'No records found');
-      // cy.verify_gtl_no_records_text();
-      // TODO: Replace with getFormFooterButtonByTypeWithText once #9689 is merged
-      cy.contains(
-        `#main-content button[type="button"]`,
-        RESET_BUTTON_TEXT
-      ).click();
-      // cy.getFormFooterButtonByTypeWithText({
-      //   buttonText: RESET_BUTTON_TEXT,
-      //   buttonWrapperClass: 'custom-button-wrapper',
-      // }).click();
-      cy.gtlGetRows([0]).then((data) => {
-        expect(data.length).to.equal(1);
-      });
+    it.only('Validate reset & apply buttons', () => {
     });
 
     afterEach(() => {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option is you can fail the test at the first line of the test, so only the setup runs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants