Playwright/E2E: Upgrade to playwright@1.43 and its dependencies (#26833)
* chore: upgrade to playwright@1.43 and its dependencies * fix lint * attempt to fix lint error on console
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
22d72b6df8
Коммит
20825101a4
@@ -224,7 +224,7 @@ $(if mme2e_is_token_in_list "webhook-interactions" "$ENABLED_DOCKER_SERVICES"; t
|
|||||||
$(if mme2e_is_token_in_list "playwright" "$ENABLED_DOCKER_SERVICES"; then
|
$(if mme2e_is_token_in_list "playwright" "$ENABLED_DOCKER_SERVICES"; then
|
||||||
echo '
|
echo '
|
||||||
playwright:
|
playwright:
|
||||||
image: mcr.microsoft.com/playwright:v1.42.1-jammy
|
image: mcr.microsoft.com/playwright:v1.43.0-jammy
|
||||||
entrypoint: ["/bin/bash", "-c"]
|
entrypoint: ["/bin/bash", "-c"]
|
||||||
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
|
command: ["until [ -f /var/run/mm_terminate ]; do sleep 5; done"]
|
||||||
env_file:
|
env_file:
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ mkdir -p logs
|
|||||||
touch logs/mattermost.log
|
touch logs/mattermost.log
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Install webapp dependencies
|
||||||
|
mme2e_log "Prepare Playwright: install webapp dependencies"
|
||||||
|
${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- playwright bash -c "cd webapp && npm install --cache /tmp/empty-cache"
|
||||||
|
|
||||||
# Install Playwright dependencies
|
# Install Playwright dependencies
|
||||||
mme2e_log "Prepare Playwright: install dependencies"
|
mme2e_log "Prepare Playwright: install dependencies"
|
||||||
${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- playwright bash -c "cd e2e-tests/playwright && rm -rf node_modules && PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --cache /tmp/empty-cache"
|
${MME2E_DC_SERVER} exec -T -u "$MME2E_UID" -- playwright bash -c "cd e2e-tests/playwright && rm -rf node_modules && PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --cache /tmp/empty-cache"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ npm run test
|
|||||||
Change to root directory, run docker container
|
Change to root directory, run docker container
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.42.1-jammy /bin/bash
|
docker run -it --rm -v "$(pwd):/mattermost/" --ipc=host mcr.microsoft.com/playwright:v1.43.0-jammy /bin/bash
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Inside the docker container
|
#### 2. Inside the docker container
|
||||||
@@ -57,7 +57,7 @@ export PW_HEADLESS=true
|
|||||||
cd mattermost/e2e-tests/playwright
|
cd mattermost/e2e-tests/playwright
|
||||||
|
|
||||||
# Install npm packages. Use "npm ci" to match the automated environment
|
# Install npm packages. Use "npm ci" to match the automated environment
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci
|
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm ci
|
||||||
|
|
||||||
# Run specific test. See https://playwright.dev/docs/test-cli.
|
# Run specific test. See https://playwright.dev/docs/test-cli.
|
||||||
npm run test -- login --project=chrome
|
npm run test -- login --project=chrome
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ async function printClientInfo(client: Client) {
|
|||||||
const {LogSettings, ServiceSettings} = await client.getConfig();
|
const {LogSettings, ServiceSettings} = await client.getConfig();
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`Notable Server Config:
|
console.log(`Notable Server Config:
|
||||||
- ServiceSettings.EnableSecurityFixAlert = ${ServiceSettings.EnableSecurityFixAlert}
|
- ServiceSettings.EnableSecurityFixAlert = ${ServiceSettings?.EnableSecurityFixAlert}
|
||||||
- LogSettings.EnableDiagnostics = ${LogSettings.EnableDiagnostics}`);
|
- LogSettings.EnableDiagnostics = ${LogSettings?.EnableDiagnostics}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensurePluginsLoaded(client: Client) {
|
async function ensurePluginsLoaded(client: Client) {
|
||||||
|
|||||||
723
e2e-tests/playwright/package-lock.json
сгенерированный
723
e2e-tests/playwright/package-lock.json
сгенерированный
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -15,12 +15,12 @@
|
|||||||
"postinstall": "script/post_install.sh"
|
"postinstall": "script/post_install.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@axe-core/playwright": "4.8.5",
|
"@axe-core/playwright": "4.9.0",
|
||||||
"@percy/cli": "1.28.1",
|
"@percy/cli": "1.28.3",
|
||||||
"@percy/playwright": "1.0.4",
|
"@percy/playwright": "1.0.5",
|
||||||
"@playwright/test": "1.42.1",
|
"@playwright/test": "1.43.1",
|
||||||
"async-wait-until": "2.0.12",
|
"async-wait-until": "2.0.12",
|
||||||
"axe-core": "4.8.4",
|
"axe-core": "4.9.0",
|
||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"dotenv": "16.4.5",
|
"dotenv": "16.4.5",
|
||||||
@@ -35,6 +35,6 @@
|
|||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.2.5",
|
||||||
"typescript": "5.4.2"
|
"typescript": "5.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const onPremServerConfig = (): Partial<TestAdminConfig> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Should be based only from the generated default config from ./server via "make config-reset"
|
// Should be based only from the generated default config from ./server via "make config-reset"
|
||||||
// Based on v9.7 server
|
// Based on v9.8 server
|
||||||
const defaultServerConfig: AdminConfig = {
|
const defaultServerConfig: AdminConfig = {
|
||||||
ServiceSettings: {
|
ServiceSettings: {
|
||||||
SiteURL: '',
|
SiteURL: '',
|
||||||
@@ -195,7 +195,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
AllowSyncedDrafts: true,
|
AllowSyncedDrafts: true,
|
||||||
UniqueEmojiReactionLimitPerPost: 50,
|
UniqueEmojiReactionLimitPerPost: 50,
|
||||||
RefreshPostStatsRunTime: '00:00',
|
RefreshPostStatsRunTime: '00:00',
|
||||||
MaximumPayloadSizeBytes: 100000,
|
MaximumPayloadSizeBytes: 300000,
|
||||||
},
|
},
|
||||||
TeamSettings: {
|
TeamSettings: {
|
||||||
SiteName: 'Mattermost',
|
SiteName: 'Mattermost',
|
||||||
@@ -319,6 +319,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
AmazonS3SSE: false,
|
AmazonS3SSE: false,
|
||||||
AmazonS3Trace: false,
|
AmazonS3Trace: false,
|
||||||
AmazonS3RequestTimeoutMilliseconds: 30000,
|
AmazonS3RequestTimeoutMilliseconds: 30000,
|
||||||
|
AmazonS3UploadPartSizeBytes: 5242880,
|
||||||
DedicatedExportStore: false,
|
DedicatedExportStore: false,
|
||||||
ExportDriverName: 'local',
|
ExportDriverName: 'local',
|
||||||
ExportDirectory: './data/',
|
ExportDirectory: './data/',
|
||||||
@@ -334,6 +335,7 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
ExportAmazonS3Trace: false,
|
ExportAmazonS3Trace: false,
|
||||||
ExportAmazonS3RequestTimeoutMilliseconds: 30000,
|
ExportAmazonS3RequestTimeoutMilliseconds: 30000,
|
||||||
ExportAmazonS3PresignExpiresSeconds: 21600,
|
ExportAmazonS3PresignExpiresSeconds: 21600,
|
||||||
|
ExportAmazonS3UploadPartSizeBytes: 104857600,
|
||||||
},
|
},
|
||||||
EmailSettings: {
|
EmailSettings: {
|
||||||
EnableSignUpWithEmail: true,
|
EnableSignUpWithEmail: true,
|
||||||
@@ -724,7 +726,9 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
ConsumePostHook: false,
|
ConsumePostHook: false,
|
||||||
CloudAnnualRenewals: false,
|
CloudAnnualRenewals: false,
|
||||||
CloudDedicatedExportUI: false,
|
CloudDedicatedExportUI: false,
|
||||||
|
ChannelBookmarks: false,
|
||||||
WebSocketEventScope: false,
|
WebSocketEventScope: false,
|
||||||
|
NotificationMonitoring: true,
|
||||||
},
|
},
|
||||||
ImportSettings: {
|
ImportSettings: {
|
||||||
Directory: './import',
|
Directory: './import',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export default class PostDotMenu {
|
|||||||
readonly removeFromSavedMenuItem;
|
readonly removeFromSavedMenuItem;
|
||||||
readonly pinToChannelMenuItem;
|
readonly pinToChannelMenuItem;
|
||||||
readonly unpinFromChannelMenuItem;
|
readonly unpinFromChannelMenuItem;
|
||||||
|
readonly moveThreadMenuItem;
|
||||||
readonly copyLinkMenuItem;
|
readonly copyLinkMenuItem;
|
||||||
readonly editMenuItem;
|
readonly editMenuItem;
|
||||||
readonly copyTextMenuItem;
|
readonly copyTextMenuItem;
|
||||||
@@ -34,6 +35,7 @@ export default class PostDotMenu {
|
|||||||
this.removeFromSavedMenuItem = getMenuItem('Remove from Saved');
|
this.removeFromSavedMenuItem = getMenuItem('Remove from Saved');
|
||||||
this.pinToChannelMenuItem = getMenuItem('Pin to Channel');
|
this.pinToChannelMenuItem = getMenuItem('Pin to Channel');
|
||||||
this.unpinFromChannelMenuItem = getMenuItem('Unpin from Channel');
|
this.unpinFromChannelMenuItem = getMenuItem('Unpin from Channel');
|
||||||
|
this.moveThreadMenuItem = getMenuItem('Move Thread');
|
||||||
this.copyLinkMenuItem = getMenuItem('Copy Link');
|
this.copyLinkMenuItem = getMenuItem('Copy Link');
|
||||||
this.editMenuItem = getMenuItem('Edit');
|
this.editMenuItem = getMenuItem('Edit');
|
||||||
this.copyTextMenuItem = getMenuItem('Copy Text');
|
this.copyTextMenuItem = getMenuItem('Copy Text');
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default class NotificationsSettings {
|
|||||||
|
|
||||||
constructor(container: Locator) {
|
constructor(container: Locator) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.keysWithHighlightDesc = container.locator('#keysWithHighlightDesc');
|
this.keysWithHighlightDesc = container.locator('#keywordsAndHighlightDesc');
|
||||||
}
|
}
|
||||||
|
|
||||||
async toBeVisible() {
|
async toBeVisible() {
|
||||||
|
|||||||
@@ -8,16 +8,14 @@ import {TestArgs} from '@e2e-types';
|
|||||||
|
|
||||||
export default async function snapshotWithPercy(name: string, testArgs: TestArgs) {
|
export default async function snapshotWithPercy(name: string, testArgs: TestArgs) {
|
||||||
if (testArgs.browserName === 'chromium' && testConfig.percyEnabled && testArgs.viewport) {
|
if (testArgs.browserName === 'chromium' && testConfig.percyEnabled && testArgs.viewport) {
|
||||||
if (!testConfig.percyToken) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('Error: Token is missing! Please set using: "export PERCY_TOKEN=<change_me>"');
|
|
||||||
}
|
|
||||||
|
|
||||||
const {page, viewport} = testArgs;
|
const {page, viewport} = testArgs;
|
||||||
|
|
||||||
// Ignore since percy is using Playwright.Page
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
await percySnapshot(page, name, {widths: [viewport.width], minHeight: viewport.height});
|
||||||
// @ts-ignore
|
} catch (error) {
|
||||||
await percySnapshot(page, name, {widths: [viewport.width], minHeight: viewport.height});
|
// log an error for debugging
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`${error}\nIn addition, check if token is properly set by "export PERCY_TOKEN=<change_me>"`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ const config: TestConfig = {
|
|||||||
// Visual tests
|
// Visual tests
|
||||||
snapshotEnabled: parseBool(process.env.PW_SNAPSHOT_ENABLE, false),
|
snapshotEnabled: parseBool(process.env.PW_SNAPSHOT_ENABLE, false),
|
||||||
percyEnabled: parseBool(process.env.PW_PERCY_ENABLE, false),
|
percyEnabled: parseBool(process.env.PW_PERCY_ENABLE, false),
|
||||||
percyToken: process.env.PERCY_TOKEN,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseBool(actualValue: string | undefined, defaultValue: boolean) {
|
function parseBool(actualValue: string | undefined, defaultValue: boolean) {
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ test('Base channel accessibility', async ({pw, pages, axe}) => {
|
|||||||
|
|
||||||
test('Post actions tab support', async ({pw, pages, axe}) => {
|
test('Post actions tab support', async ({pw, pages, axe}) => {
|
||||||
// # Create and sign in a new user
|
// # Create and sign in a new user
|
||||||
const {user} = await pw.initSetup();
|
const {user, adminClient} = await pw.initSetup();
|
||||||
|
const config = await adminClient.getConfig();
|
||||||
|
const license = await adminClient.getClientLicenseOld();
|
||||||
|
|
||||||
// # Log in a user in new browser context
|
// # Log in a user in new browser context
|
||||||
const {page} = await pw.testBrowser.login(user);
|
const {page} = await pw.testBrowser.login(user);
|
||||||
@@ -85,9 +87,19 @@ test('Post actions tab support', async ({pw, pages, axe}) => {
|
|||||||
await channelsPage.postDotMenu.saveMenuItem.press('ArrowDown');
|
await channelsPage.postDotMenu.saveMenuItem.press('ArrowDown');
|
||||||
await expect(channelsPage.postDotMenu.pinToChannelMenuItem).toBeFocused();
|
await expect(channelsPage.postDotMenu.pinToChannelMenuItem).toBeFocused();
|
||||||
|
|
||||||
// * Should move focus to Copy Link after arrow down
|
if (config.FeatureFlags['MoveThreadsEnabled'] && license.IsLicensed === 'true') {
|
||||||
await channelsPage.postDotMenu.pinToChannelMenuItem.press('ArrowDown');
|
// * Should move focus to Move Thread after arrow down
|
||||||
await expect(channelsPage.postDotMenu.copyLinkMenuItem).toBeFocused();
|
await channelsPage.postDotMenu.pinToChannelMenuItem.press('ArrowDown');
|
||||||
|
await expect(channelsPage.postDotMenu.moveThreadMenuItem).toBeFocused();
|
||||||
|
|
||||||
|
// * Should move focus to Copy Link after arrow down
|
||||||
|
await channelsPage.postDotMenu.moveThreadMenuItem.press('ArrowDown');
|
||||||
|
await expect(channelsPage.postDotMenu.copyLinkMenuItem).toBeFocused();
|
||||||
|
} else {
|
||||||
|
// * Should move focus to Copy Link after arrow down
|
||||||
|
await channelsPage.postDotMenu.pinToChannelMenuItem.press('ArrowDown');
|
||||||
|
await expect(channelsPage.postDotMenu.copyLinkMenuItem).toBeFocused();
|
||||||
|
}
|
||||||
|
|
||||||
// * Should move focus to Edit after arrow down
|
// * Should move focus to Edit after arrow down
|
||||||
await channelsPage.postDotMenu.copyLinkMenuItem.press('ArrowDown');
|
await channelsPage.postDotMenu.copyLinkMenuItem.press('ArrowDown');
|
||||||
|
|||||||
@@ -40,19 +40,19 @@ test('MM-T5465-1 Should add the keyword when enter, comma or tab is pressed on t
|
|||||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||||
|
|
||||||
// # Enter keyword 1
|
// # Enter keyword 1
|
||||||
await keywordsInput.type(keywords[0]);
|
await keywordsInput.fill(keywords[0]);
|
||||||
|
|
||||||
// # Press Comma on the textbox
|
// # Press Comma on the textbox
|
||||||
await keywordsInput.press(',');
|
await keywordsInput.press(',');
|
||||||
|
|
||||||
// # Enter keyword 2
|
// # Enter keyword 2
|
||||||
await keywordsInput.type(keywords[1]);
|
await keywordsInput.fill(keywords[1]);
|
||||||
|
|
||||||
// # Press Tab on the textbox
|
// # Press Tab on the textbox
|
||||||
await keywordsInput.press('Tab');
|
await keywordsInput.press('Tab');
|
||||||
|
|
||||||
// # Enter keyword 3
|
// # Enter keyword 3
|
||||||
await keywordsInput.type(keywords[2]);
|
await keywordsInput.fill(keywords[2]);
|
||||||
|
|
||||||
// # Press Enter on the textbox
|
// # Press Enter on the textbox
|
||||||
await keywordsInput.press('Enter');
|
await keywordsInput.press('Enter');
|
||||||
@@ -94,7 +94,7 @@ test('MM-T5465-2 Should highlight the keywords when a message is sent with the k
|
|||||||
|
|
||||||
// # Enter the keyword
|
// # Enter the keyword
|
||||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||||
await keywordsInput.type(keywords[3]);
|
await keywordsInput.fill(keywords[3]);
|
||||||
await keywordsInput.press('Tab');
|
await keywordsInput.press('Tab');
|
||||||
|
|
||||||
// # Save the keyword
|
// # Save the keyword
|
||||||
@@ -150,7 +150,7 @@ test('MM-T5465-3 Should highlight the keywords when a message is sent with the k
|
|||||||
|
|
||||||
// # Enter the keyword
|
// # Enter the keyword
|
||||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||||
await keywordsInput.type(keywords[3]);
|
await keywordsInput.fill(keywords[3]);
|
||||||
await keywordsInput.press('Tab');
|
await keywordsInput.press('Tab');
|
||||||
|
|
||||||
// # Save the keyword
|
// # Save the keyword
|
||||||
@@ -208,7 +208,7 @@ test('MM-T5465-4 Highlighted keywords should not appear in the Recent Mentions',
|
|||||||
|
|
||||||
// # Enter the keyword
|
// # Enter the keyword
|
||||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||||
await keywordsInput.type(keywords[0]);
|
await keywordsInput.fill(keywords[0]);
|
||||||
await keywordsInput.press('Tab');
|
await keywordsInput.press('Tab');
|
||||||
|
|
||||||
// # Save the keyword
|
// # Save the keyword
|
||||||
@@ -270,7 +270,7 @@ test('MM-T5465-5 Should highlight keywords in message sent from another user', a
|
|||||||
|
|
||||||
// # Enter the keyword
|
// # Enter the keyword
|
||||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||||
await keywordsInput.type(keywords[0]);
|
await keywordsInput.fill(keywords[0]);
|
||||||
await keywordsInput.press('Tab');
|
await keywordsInput.press('Tab');
|
||||||
|
|
||||||
// # Save the keyword
|
// # Save the keyword
|
||||||
|
|||||||
Двоичный файл не отображается.
|
До Ширина: | Высота: | Размер: 69 KiB После Ширина: | Высота: | Размер: 82 KiB |
Двоичный файл не отображается.
|
До Ширина: | Высота: | Размер: 101 KiB После Ширина: | Высота: | Размер: 110 KiB |
Двоичный файл не отображается.
|
До Ширина: | Высота: | Размер: 68 KiB После Ширина: | Высота: | Размер: 80 KiB |
@@ -30,7 +30,6 @@ export type TestConfig = {
|
|||||||
// Visual tests
|
// Visual tests
|
||||||
snapshotEnabled: boolean;
|
snapshotEnabled: boolean;
|
||||||
percyEnabled: boolean;
|
percyEnabled: boolean;
|
||||||
percyToken?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Based on https://github.com/microsoft/playwright/blob/d6ec1ae3994f127e38b866a231a34efc6a4cac0d/packages/playwright/types/test.d.ts#L5692-L5759
|
// Based on https://github.com/microsoft/playwright/blob/d6ec1ae3994f127e38b866a231a34efc6a4cac0d/packages/playwright/types/test.d.ts#L5692-L5759
|
||||||
|
|||||||
@@ -531,6 +531,7 @@ export type FileSettings = {
|
|||||||
AmazonS3SSE: boolean;
|
AmazonS3SSE: boolean;
|
||||||
AmazonS3Trace: boolean;
|
AmazonS3Trace: boolean;
|
||||||
AmazonS3RequestTimeoutMilliseconds: number;
|
AmazonS3RequestTimeoutMilliseconds: number;
|
||||||
|
AmazonS3UploadPartSizeBytes: number;
|
||||||
DedicatedExportStore: boolean;
|
DedicatedExportStore: boolean;
|
||||||
ExportDriverName: string;
|
ExportDriverName: string;
|
||||||
ExportDirectory: string;
|
ExportDirectory: string;
|
||||||
@@ -546,6 +547,7 @@ export type FileSettings = {
|
|||||||
ExportAmazonS3Trace: boolean;
|
ExportAmazonS3Trace: boolean;
|
||||||
ExportAmazonS3RequestTimeoutMilliseconds: number;
|
ExportAmazonS3RequestTimeoutMilliseconds: number;
|
||||||
ExportAmazonS3PresignExpiresSeconds: number;
|
ExportAmazonS3PresignExpiresSeconds: number;
|
||||||
|
ExportAmazonS3UploadPartSizeBytes: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmailSettings = {
|
export type EmailSettings = {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user