enhance: e2eテストをできるだけ改良してみた (#8159)

* update docker image?

* 続

* serial run delete from "${table}" cascade

* use cypress official github action

* refuse install by cypress action

* clean up

* use wait?

* use more wait?

* Revert "use more wait?"

This reverts commit 18d0fcae9c.

* Revert "use wait?"

This reverts commit 5aa8feec9c.

* fix

* test

* test

* log?

* 握りつぶしてみる

* clean up

* env?

* clean up?

* disable video

* add comment

* remove test

* 成功?

* test browser

* nodeインストール無効化

* node16.13.0-chrome95-ff94

* node.js復活

* ?

* ちょっと戻してみる

* chrome?

* cross browser test2

* --shm-size=2g

* artifact?

* misskey.local?

* firefoxはあきらめる

* not headless?

* oops

* fix

* ??

* test1

* if?

* fail-fast: false

* headless: false

* easy error ignoreing describe

* エラーの解消
とちょっとリファクター

* add browser name to artifact

* Install mplayer for FireFox

* no wait?

* タイムアウトを甘くしてみる

* firefoxをあきらめる(n回目)

* remove timeout setting

* wait復活

* Update basic.js

* Update index.js

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
This commit is contained in:
tamaina 2022-01-21 16:43:56 +09:00 committed by GitHub
parent cbb7e95d82
commit e1d69e236f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 99 additions and 71 deletions

View file

@ -17,14 +17,14 @@ jobs:
services: services:
postgres: postgres:
image: postgres:12.2-alpine image: postgres:13
ports: ports:
- 54312:5432 - 54312:5432
env: env:
POSTGRES_DB: test-misskey POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
redis: redis:
image: redis:4.0-alpine image: redis:6
ports: ports:
- 56312:6379 - 56312:6379
@ -51,19 +51,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]
browser: [chrome]
services: services:
postgres: postgres:
image: postgres:12.2-alpine image: postgres:13
ports: ports:
- 54312:5432 - 54312:5432
env: env:
POSTGRES_DB: test-misskey POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
redis: redis:
image: redis:4.0-alpine image: redis:6
ports: ports:
- 56312:6379 - 56312:6379
@ -71,6 +73,12 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: true submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150
#- name: Install mplayer for FireFox
# run: sudo apt install mplayer -y
# if: ${{ matrix.browser == 'firefox' }}
#- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }}
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
@ -87,5 +95,24 @@ jobs:
run: cp .github/misskey/test.yml .config run: cp .github/misskey/test.yml .config
- name: Build - name: Build
run: yarn build run: yarn build
- name: Test # https://github.com/cypress-io/cypress/issues/4351#issuecomment-559489091
run: yarn e2e - name: ALSA Env
run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc
- name: Cypress run
uses: cypress-io/github-action@v2
with:
install: false
start: npm run start:test
wait-on: 'http://localhost:61812'
headless: false
browser: ${{ matrix.browser }}
- uses: actions/upload-artifact@v2
if: failure()
with:
name: ${{ matrix.browser }}-cypress-screenshots
path: cypress/screenshots
- uses: actions/upload-artifact@v2
if: always()
with:
name: ${{ matrix.browser }}-cypress-videos
path: cypress/videos

View file

@ -41,8 +41,6 @@ describe('After setup instance', () => {
username: 'admin', username: 'admin',
password: 'pass', password: 'pass',
}).its('body').as('admin'); }).its('body').as('admin');
cy.get('@admin');
}); });
afterEach(() => { afterEach(() => {
@ -82,7 +80,6 @@ describe('After user signup', () => {
password: 'pass', password: 'pass',
}).its('body').as('admin'); }).its('body').as('admin');
cy.get('@admin').then(() => {
// ユーザー作成 // ユーザー作成
cy.request('POST', '/api/signup', { cy.request('POST', '/api/signup', {
username: 'alice', username: 'alice',
@ -90,9 +87,6 @@ describe('After user signup', () => {
}).its('body').as('alice'); }).its('body').as('alice');
}); });
cy.get('@alice');
});
afterEach(() => { afterEach(() => {
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。 // テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
// waitを入れることでそれを防止できる // waitを入れることでそれを防止できる
@ -145,15 +139,12 @@ describe('After user singed in', () => {
password: 'pass', password: 'pass',
}).its('body').as('admin'); }).its('body').as('admin');
cy.get('@admin').then(() => {
// ユーザー作成 // ユーザー作成
cy.request('POST', '/api/signup', { cy.request('POST', '/api/signup', {
username: 'alice', username: 'alice',
password: 'alice1234', password: 'alice1234',
}).its('body').as('alice'); }).its('body').as('alice');
});
cy.get('@alice').then(() => {
cy.visit('/'); cy.visit('/');
cy.intercept('POST', '/api/signin').as('signin'); cy.intercept('POST', '/api/signin').as('signin');
@ -165,9 +156,6 @@ describe('After user singed in', () => {
cy.wait('@signin').as('signedIn'); cy.wait('@signin').as('signedIn');
}); });
cy.get('@signedIn');
});
afterEach(() => { afterEach(() => {
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。 // テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
// waitを入れることでそれを防止できる // waitを入れることでそれを防止できる

View file

@ -20,7 +20,13 @@ import './commands'
// require('./commands') // require('./commands')
Cypress.on('uncaught:exception', (err, runnable) => { Cypress.on('uncaught:exception', (err, runnable) => {
if (err.message.includes('ResizeObserver loop limit exceeded')) { if ([
return false // Chrome
'ResizeObserver loop limit exceeded',
// Firefox
'ResizeObserver loop completed with undelivered notifications',
].some(msg => err.message.includes(msg))) {
return false;
} }
}); });

View file

@ -220,7 +220,9 @@ export async function resetDb() {
WHERE nspname NOT IN ('pg_catalog', 'information_schema') WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind = 'r' AND C.relkind = 'r'
AND nspname !~ '^pg_toast';`); AND nspname !~ '^pg_toast';`);
await Promise.all(tables.map(t => t.table).map(x => conn.query(`DELETE FROM "${x}" CASCADE`))); for (const table of tables) {
await conn.query(`DELETE FROM "${table.table}" CASCADE`);
}
}; };
for (let i = 1; i <= 3; i++) { for (let i = 1; i <= 3; i++) {

View file

@ -2,12 +2,12 @@ version: "3"
services: services:
redistest: redistest:
image: redis:4.0-alpine image: redis:6
ports: ports:
- "127.0.0.1:56312:6379" - "127.0.0.1:56312:6379"
dbtest: dbtest:
image: postgres:12.2-alpine image: postgres:13
ports: ports:
- "127.0.0.1:54312:5432" - "127.0.0.1:54312:5432"
environment: environment:

View file

@ -32,9 +32,7 @@ const props = defineProps<{
const pagingComponent = ref<InstanceType<typeof MkPagination>>(); const pagingComponent = ref<InstanceType<typeof MkPagination>>();
defineExpose({ defineExpose({
prepend: (note) => { pagingComponent,
pagingComponent.value?.prepend(note);
},
}); });
</script> </script>

View file

@ -25,10 +25,10 @@ const emit = defineEmits<{
provide('inChannel', computed(() => props.src === 'channel')); provide('inChannel', computed(() => props.src === 'channel'));
const tlComponent = ref<InstanceType<typeof XNotes>>(); const tlComponent: InstanceType<typeof XNotes> = $ref();
const prepend = note => { const prepend = note => {
tlComponent.value.prepend(note); tlComponent.pagingComponent?.prepend(note);
emit('note'); emit('note');
@ -38,16 +38,16 @@ const prepend = note => {
}; };
const onUserAdded = () => { const onUserAdded = () => {
tlComponent.value.reload(); tlComponent.pagingComponent?.reload();
}; };
const onUserRemoved = () => { const onUserRemoved = () => {
tlComponent.value.reload(); tlComponent.pagingComponent?.reload();
}; };
const onChangeFollowing = () => { const onChangeFollowing = () => {
if (!tlComponent.value.backed) { if (!tlComponent.pagingComponent?.backed) {
tlComponent.value.reload(); tlComponent.pagingComponent?.reload();
} }
}; };

View file

@ -73,12 +73,11 @@ const queue = ref<Item[]>([]);
const offset = ref(0); const offset = ref(0);
const fetching = ref(true); const fetching = ref(true);
const moreFetching = ref(false); const moreFetching = ref(false);
const inited = ref(false);
const more = ref(false); const more = ref(false);
const backed = ref(false); // const backed = ref(false); //
const isBackTop = ref(false); const isBackTop = ref(false);
const empty = computed(() => items.value.length === 0 && !fetching.value && inited.value); const empty = computed(() => items.value.length === 0);
const error = computed(() => !fetching.value && !inited.value); const error = ref(false);
const init = async (): Promise<void> => { const init = async (): Promise<void> => {
queue.value = []; queue.value = [];
@ -105,9 +104,10 @@ const init = async (): Promise<void> => {
more.value = false; more.value = false;
} }
offset.value = res.length; offset.value = res.length;
inited.value = true; error.value = false;
fetching.value = false; fetching.value = false;
}, e => { }, e => {
error.value = true;
fetching.value = false; fetching.value = false;
}); });
}; };
@ -183,9 +183,8 @@ const fetchMoreAhead = async (): Promise<void> => {
}; };
const prepend = (item: Item): void => { const prepend = (item: Item): void => {
if (rootEl.value == null) return;
if (props.pagination.reversed) { if (props.pagination.reversed) {
if (rootEl.value) {
const container = getScrollContainer(rootEl.value); const container = getScrollContainer(rootEl.value);
if (container == null) return; // TODO? if (container == null) return; // TODO?
@ -204,9 +203,16 @@ const prepend = (item: Item): void => {
more.value = true; more.value = true;
} }
} }
}
items.value.push(item); items.value.push(item);
// TODO // TODO
} else { } else {
// unshiftOK
if (!rootEl.value) {
items.value.unshift(item);
return;
}
const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value)); const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
if (isTop) { if (isTop) {
@ -264,6 +270,7 @@ onDeactivated(() => {
defineExpose({ defineExpose({
items, items,
backed,
reload, reload,
fetchMoreAhead, fetchMoreAhead,
prepend, prepend,

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="_section"> <div class="_section">
<XNotes ref="notes" class="_content" :pagination="pagination"/> <XNotes class="_content" :pagination="pagination"/>
</div> </div>
</template> </template>

View file

@ -5,7 +5,7 @@
<option value="replies">{{ $ts.notesAndReplies }}</option> <option value="replies">{{ $ts.notesAndReplies }}</option>
<option value="files">{{ $ts.withFiles }}</option> <option value="files">{{ $ts.withFiles }}</option>
</MkTab> </MkTab>
<XNotes ref="timeline" :no-gap="true" :pagination="pagination"/> <XNotes :no-gap="true" :pagination="pagination"/>
</div> </div>
</template> </template>