Extension Registry: QA Added acceptance tests for readOnlyGuards rule, entity action and custom property editor (#19939)

* Added tests for readOnlyGuard rules

* Added backoffice override files for readOnlyGuard tests

* Bumped version

* Added project for ExtensionRegistry tests in playwright configs

* Updated nightly E2E test pipelines to run Extension Registry tests

* Updated nightly E2E test pipeline

* Updated nightly E2E test pipeline

* Updated playwright configs

* Updated nightly E2E test pipeline

* Add test for Entity Action Extension to retrieve entityType and unique (#20020)

* Add entity action test to get unique and entity type

* update test entity action

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>

* Added job to run the Extension Registry tests in the nightly pipeline

* Cleaned up

* Restructure AdditionSetup folder for extension registry

* Updated yaml file for nightly E2E pipeline

* Updated json file for lock action

* Skip test for content delivery API

* Updated port

* Comment out others to run only extension registry tests

* Updated port

* Remove retrieve action folder to test

* Reverted nightly E2E test pipeline

* Reverted

* Updated umbraco package json

* Reverted

* Renamed AdditionalSetup folder

* Renamed folder

* Added appsetting.json file

* Updated appsettings.json

* Updated appsettings.json

* Added debug step

* Added step to build backoffice

* Reverted

* Only spec.ts file run in the extension registry project

* Property Editor: Add tests for create and using custom property editor (#20213)

* Property Editor: tests for create and using custom property editor

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/PropertyEditorTest.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/PropertyEditorTest.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/PropertyEditorTest.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* update review from Nhu

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/CustomPropertyEditor.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* Update tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/CustomPropertyEditor.spec.ts

Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* fix comment from Nhu

---------

Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com>

* Format code

* Fixed

* Format code

* Format code

* Format code

* Updated indentation

* Fixed comments

* change the name of test

---------

Co-authored-by: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com>
Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk>
Co-authored-by: Andreas Zerbst <andr317c@live.dk>
This commit is contained in:
Nhu Dinh
2025-10-06 16:47:06 +07:00
committed by GitHub
parent e7fde8c01f
commit 84fb1f4d6c
16 changed files with 496 additions and 3 deletions

View File

@@ -526,6 +526,23 @@ stages:
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: true
# ExtensionRegistry
WindowsExtensionRegistry:
vmImage: "windows-latest"
testFolder: "ExtensionRegistry"
port: ''
testCommand: "npx playwright test --project=extensionRegistry"
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: false
LinuxExtensionRegistry:
vmImage: "ubuntu-latest"
testFolder: "ExtensionRegistry"
port: ''
testCommand: "npx playwright test --project=extensionRegistry"
CONNECTIONSTRINGS__UMBRACODBDSN: Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
additionalEnvironmentVariables: false
pool:
vmImage: $(vmImage)
steps:
@@ -657,4 +674,4 @@ stages:
--data "$PAYLOAD" \
"$SLACK_WEBHOOK_URL"
env:
SLACK_WEBHOOK_URL: $(E2ESLACKWEBHOOKURL)
SLACK_WEBHOOK_URL: $(E2ESLACKWEBHOOKURL)

View File

@@ -54,6 +54,17 @@ export default defineConfig({
storageState: STORAGE_STATE
}
},
{
name: 'extensionRegistry',
testMatch: 'ExtensionRegistry/*.spec.ts',
dependencies: ['setup'],
use: {
...devices['Desktop Chrome'],
// Use prepared auth state.
ignoreHTTPSErrors: true,
storageState: STORAGE_STATE
}
},
{
name: 'deliveryApi',
testMatch: 'DeliveryApi/**',
@@ -63,7 +74,7 @@ export default defineConfig({
// Use prepared auth state.
ignoreHTTPSErrors: true,
storageState: STORAGE_STATE
},
}
},
{
name: 'externalLoginAzureADB2C',

View File

@@ -1,7 +1,7 @@
import {expect} from '@playwright/test';
import {AliasHelper, test} from '@umbraco/playwright-testhelpers';
test('can get content from delivery api', async ({umbracoApi}) => {
test.skip('can get content from delivery api', async ({umbracoApi}) => {
// Arrange
const documentTypeName = 'TestDocumentType';
const contentName = 'TestContent';

View File

@@ -0,0 +1,100 @@
import { css as p, property as u, state as c, customElement as g, html as l } from "@umbraco-cms/backoffice/external/lit";
import { UmbLitElement as m } from "@umbraco-cms/backoffice/lit-element";
import { UmbTextStyles as v } from "@umbraco-cms/backoffice/style";
var x = Object.defineProperty, _ = Object.getOwnPropertyDescriptor, o = (t, e, a, n) => {
for (var r = n > 1 ? void 0 : n ? _(e, a) : e, i = t.length - 1, h; i >= 0; i--)
(h = t[i]) && (r = (n ? h(e, a, r) : h(r)) || r);
return n && r && x(e, a, r), r;
};
let s = class extends m {
constructor() {
super(...arguments), this.value = "";
}
render() {
const t = this.value?.length || 0, a = this._maxLength !== null && this._maxLength !== void 0 && this._maxLength > 0;
let n = "";
return this._maxLength && (t > this._maxLength * 0.9 ? n = "danger" : t > this._maxLength * 0.7 && (n = "warning")), l`
<uui-input
class="text-input"
type="text"
.value=${this.value || ""}
.placeholder=${this._placeholder || ""}
.maxlength=${this._maxLength || ""}
@input=${this._onInput}
/></uui-input>
${a ? l`
<div class="char-counter ${n}">
${t}/${this._maxLength}
</div>
` : ""}
`;
}
connectedCallback() {
super.connectedCallback(), this._updateConfigValues();
}
_updateConfigValues() {
this._maxLength = this.config?.getValueByAlias("maxChars"), this._placeholder = this.config?.getValueByAlias("placeholder") || "Enter text here...";
}
_onInput(t) {
const e = t.target, a = e.value;
if (this._maxLength && a.length > this._maxLength) {
e.value = this.value;
return;
}
this.value = a, this._dispatchChangeEvent();
}
_dispatchChangeEvent() {
this.dispatchEvent(
new CustomEvent("property-value-change", {
detail: {
value: this.value
},
bubbles: !0,
composed: !0
})
);
}
};
s.styles = [
v,
p`
.text-input{
width: 100%;
}
.char-counter {
position: absolute;
bottom: -20px;
right: 0;
font-size: 12px;
color: var(--uui-color-text-alt);
}
.char-counter.warning {
color: var(--uui-color-warning);
}
.char-counter.danger {
color: var(--uui-color-danger);
}
`
];
o([
u({ type: String })
], s.prototype, "value", 2);
o([
u({ attribute: !1 })
], s.prototype, "config", 2);
o([
c()
], s.prototype, "_maxLength", 2);
o([
c()
], s.prototype, "_placeholder", 2);
s = o([
g("custom-text-editor")
], s);
export {
s as CustomTextEditorElement,
s as default
};
//# sourceMappingURL=customtexteditor.js.map

View File

@@ -0,0 +1,18 @@
{
"$schema": "../../umbraco-package-schema.json",
"name": "My.Extension",
"extensions": [
{
"type": "propertyEditorUi",
"alias": "Custom.TextEditor",
"name": "Custom Text Editor",
"element": "/App_Plugins/custom-property-editor/dist/customtexteditor.js",
"meta": {
"label": "Custom Text Editor",
"propertyEditorSchemaAlias": "Umbraco.TextBox",
"icon": "icon-edit",
"group": "Custom"
}
}
]
}

View File

@@ -0,0 +1,25 @@
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
export class UmbDocumentLockEntityAction extends UmbEntityActionBase {
async execute() {
const context = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT);
const ruleUnique = 'lock';
const myRule = {
unique: ruleUnique,
UmbVariantId: new UmbVariantId(),
};
const hasRule = context?.readOnlyGuard.getRules().find((rule) => rule.unique === ruleUnique);
if (hasRule) {
context?.readOnlyGuard.removeRule(ruleUnique);
} else {
context?.readOnlyGuard.addRule(myRule);
}
}
}
export { UmbDocumentLockEntityAction as api };

View File

@@ -0,0 +1,17 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '@umbraco-cms/backoffice/document';
export const manifests = [
{
type: 'entityAction',
kind: 'default',
alias: 'Umb.EntityAction.Document.Lock',
name: 'Lock Document Entity Action',
forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE],
api: () => import('./lock-action.api.js'),
weight: 200,
meta: {
label: 'Lock it',
icon: 'icon-lock',
}
}
]

View File

@@ -0,0 +1,12 @@
{
"name": "E2E Test Package",
"version": "1.0.0",
"extensions": [
{
"type": "bundle",
"alias": "E2E.Package.Bundle",
"name": "E2E Test Package Bundle",
"js": "/App_Plugins/lock-action/lock-action.manifests.js"
}
]
}

View File

@@ -0,0 +1,18 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
export class RetrieveAction extends UmbEntityActionBase {
async execute() {
const { entityType, unique } = this.args;
const message = `${entityType}_${unique}`;
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
notificationContext?.peek('positive', {
data: {
headline: '',
message: message,
},
});
}
}
export { RetrieveAction as api };

View File

@@ -0,0 +1,13 @@
export const manifest = {
type: 'entityAction',
kind: 'default',
alias: 'Retrieve',
name: 'Retrieve',
weight: 200,
forEntityTypes: ['document', 'media'],
api: () => import('./retrieve-action.api.js'),
meta: {
icon: 'icon-add',
label: 'Retrieve'
},
};

View File

@@ -0,0 +1,12 @@
{
"name": "My entity action",
"version": "1.0.0",
"extensions": [
{
"type": "bundle",
"alias": "Umb.EntityAction.Test.Bundle",
"name": "Test entity action bundle",
"js": "/App_Plugins/retrieve-action/retrieve-action.manifests.js"
}
]
}

View File

@@ -0,0 +1,58 @@
{
"$schema": "appsettings-schema.json",
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"Configure": [
{
"Name": "Console"
}
]
}
}
]
},
"Umbraco": {
"CMS": {
"Unattended": {
"InstallUnattended": true,
"UnattendedUserName": "Playwright Test",
"UnattendedUserEmail": "playwright@umbraco.com",
"UnattendedUserPassword": "UmbracoAcceptance123!"
},
"Content": {
"ContentVersionCleanupPolicy": {
"EnableCleanup": false
}
},
"Global": {
"DisableElectionForSingleServer": true,
"InstallMissingDatabase": true,
"Id": "00000000-0000-0000-0000-000000000042",
"VersionCheckPeriod": 0,
"UseHttps": true
},
"HealthChecks": {
"Notification": {
"Enabled": false
}
},
"KeepAlive": {
"DisableKeepAliveTask": true
},
"WebRouting": {
"UmbracoApplicationUrl": "https://localhost:44331/"
}
}
}
}

View File

@@ -0,0 +1,87 @@
import {expect} from '@playwright/test';
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
// Content
const contentName = 'TestContent';
// DocumentType
const documentTypeName = 'TestDocumentTypeForContent';
// DataType
const dataTypeName = 'CustomTextBox';
const editorUiAlias = 'Custom.TextEditor';
const editorAlias = 'Umbraco.TextBox';
// Property Editor
const customPropertyEditorName = 'Custom Text Editor';
// Test values
const testValue = 'This is a test value for the custom property editor';
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.dataType.ensureNameNotExists(dataTypeName);
});
test('can add custom property editor to a document type', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.goToBackOffice();
await umbracoUi.dataType.goToSection(ConstantHelper.sections.settings);
// Act
await umbracoUi.dataType.clickActionsMenuAtRoot();
await umbracoUi.dataType.clickCreateActionMenuOption();
await umbracoUi.dataType.clickDataTypeButton();
await umbracoUi.dataType.enterDataTypeName(dataTypeName);
await umbracoUi.dataType.clickSelectAPropertyEditorButton();
await umbracoUi.dataType.selectAPropertyEditor(customPropertyEditorName);
await umbracoUi.dataType.clickSaveButton();
// Assert
await umbracoUi.dataType.waitForDataTypeToBeCreated();
await umbracoUi.dataType.isDataTypeTreeItemVisible(dataTypeName);
expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy();
});
test('can select custom property editor in property editor picker on data type', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
const dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, []);
await umbracoUi.goToBackOffice();
await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
// Act
await umbracoUi.documentType.goToDocumentType(documentTypeName);
await umbracoUi.documentType.clickAddGroupButton();
await umbracoUi.documentType.addPropertyEditor(dataTypeName);
await umbracoUi.documentType.enterGroupName('Content');
await umbracoUi.documentType.clickSaveButton();
// Assert
await umbracoUi.documentType.waitForDocumentTypeToBeCreated();
expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
// Checks if the correct property was added to the document type
expect(documentTypeData.properties[0].dataType.id).toBe(dataTypeId);
});
test('can write and read value from custom property editor', async ({umbracoApi, umbracoUi}) => {
// Arrange
const dataTypeValue = [
{
alias: "maxChars",
value: "100",
},
];
const dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeValue);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId);
await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, "Test content", dataTypeName);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.enterPropertyValue(dataTypeName, testValue);
await umbracoUi.content.clickSaveButton();
// Assert
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.values[0].value).toEqual(testValue);
});

View File

@@ -0,0 +1,56 @@
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
// Content
const contentName = 'TestContent';
const contentText = 'This is test content text';
// DocumentType
const documentTypeName = 'TestDocumentTypeForContent';
// DataType
const dataTypeName = 'Textstring';
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.language.ensureIsoCodeNotExists("da");
});
test('can lock an invariant content node', {tag: '@release'}, async ({umbracoApi, umbracoUi}) => {
// Arrange
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.isDocumentReadOnly(false);
await umbracoUi.content.clickWorkspaceActionMenuButton();
await umbracoUi.content.clickLockActionMenuOption();
// Assert
await umbracoUi.content.isContentNameReadOnly();
await umbracoUi.content.isDocumentReadOnly(true);
await umbracoUi.content.isPropertyEditorUiWithNameReadOnly("text-box");
});
test('can lock a variant content node', async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoApi.language.createDanishLanguage();
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
const documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithInvariantPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.isDocumentReadOnly(false);
await umbracoUi.content.clickWorkspaceActionMenuButton();
await umbracoUi.content.clickLockActionMenuOption();
// Assert
await umbracoUi.content.isContentNameReadOnly();
await umbracoUi.content.isDocumentReadOnly(true);
await umbracoUi.content.isPropertyEditorUiWithNameReadOnly("text-box");
});

View File

@@ -0,0 +1,48 @@
import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
// Content
const contentName = 'TestContent';
// DocumentType
const documentTypeName = 'TestDocumentTypeForContent';
// DataType
const dataTypeName = 'Textstring';
//Media
const mediaName = 'TestMedia';
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.media.ensureNameNotExists(mediaName);
});
test('can retrieve unique id and entity type of document', async ({umbracoApi, umbracoUi}) => {
// Arrange
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
const documentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, 'Test content', dataTypeName);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickWorkspaceActionMenuButton();
await umbracoUi.content.clickEntityActionWithName('Retrieve');
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText('document_' + documentId);
});
test('can retrieve unique id and entity type of media', async ({umbracoApi, umbracoUi}) => {
// Arrange
const mediaId = await umbracoApi.media.createDefaultMediaWithImage(mediaName);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.media);
// Act
await umbracoUi.media.goToMediaWithName(mediaName);
await umbracoUi.media.clickWorkspaceActionMenuButton();
await umbracoUi.media.clickEntityActionWithName('Retrieve');
// Assert
await umbracoUi.media.doesSuccessNotificationHaveText('media_' + mediaId);
});