Merge pull request #2283 from krille-chan/krille/maestro-integration-tests
build: Add maestro based integration tests
This commit is contained in:
commit
caa5847b66
15 changed files with 353 additions and 633 deletions
144
.github/workflows/integrate.yaml
vendored
144
.github/workflows/integrate.yaml
vendored
|
|
@ -45,9 +45,11 @@ jobs:
|
||||||
- run: flutter test
|
- run: flutter test
|
||||||
|
|
||||||
build_debug_apk:
|
build_debug_apk:
|
||||||
|
needs: [ code_tests ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
- uses: ./.github/actions/free_up_space
|
||||||
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
- run: cat .github/workflows/versions.env >> $GITHUB_ENV
|
||||||
- uses: actions/setup-java@v5
|
- uses: actions/setup-java@v5
|
||||||
with:
|
with:
|
||||||
|
|
@ -57,12 +59,27 @@ jobs:
|
||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
cache: true
|
cache: true
|
||||||
- uses: ./.github/actions/free_up_space
|
|
||||||
- uses: moonrepo/setup-rust@v1
|
- uses: moonrepo/setup-rust@v1
|
||||||
- run: flutter pub get
|
with:
|
||||||
- run: flutter build apk --debug --target-platform android-arm64
|
cache: true
|
||||||
|
- name: Cache Gradle
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: gradle-${{ runner.os }}-
|
||||||
|
- run: ./scripts/add-firebase-messaging.sh
|
||||||
|
- run: flutter build apk --debug --target-platform android-x64
|
||||||
|
- name: Upload Debug APK
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: debug-apk-x64
|
||||||
|
path: build/app/outputs/flutter-apk/app-debug.apk
|
||||||
|
|
||||||
build_debug_web:
|
build_debug_web:
|
||||||
|
needs: [ code_tests ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
@ -76,9 +93,15 @@ jobs:
|
||||||
- run: flutter pub get
|
- run: flutter pub get
|
||||||
- name: Prepare web
|
- name: Prepare web
|
||||||
run: ./scripts/prepare-web.sh
|
run: ./scripts/prepare-web.sh
|
||||||
- run: flutter build web
|
- run: flutter build web --dart-define=WITH_SEMANTICS=true
|
||||||
|
- name: Upload Web Build
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: Web Build
|
||||||
|
path: build/web
|
||||||
|
|
||||||
build_debug_linux:
|
build_debug_linux:
|
||||||
|
needs: [ code_tests ]
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch: [ x64, arm64 ]
|
arch: [ x64, arm64 ]
|
||||||
|
|
@ -97,6 +120,7 @@ jobs:
|
||||||
- run: ./flutter/bin/flutter build linux --target-platform linux-${{ matrix.arch }}
|
- run: ./flutter/bin/flutter build linux --target-platform linux-${{ matrix.arch }}
|
||||||
|
|
||||||
build_debug_ios:
|
build_debug_ios:
|
||||||
|
needs: [ code_tests ]
|
||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
@ -115,3 +139,115 @@ jobs:
|
||||||
sed -i '' 's,//<GOOGLE_SERVICES>,,g' lib/utils/background_push.dart
|
sed -i '' 's,//<GOOGLE_SERVICES>,,g' lib/utils/background_push.dart
|
||||||
- run: flutter pub get
|
- run: flutter pub get
|
||||||
- run: flutter build ios --no-codesign
|
- run: flutter build ios --no-codesign
|
||||||
|
|
||||||
|
integration_test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
needs: [ build_debug_apk ]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
api-level: [34]
|
||||||
|
env:
|
||||||
|
ANDROID_USER_HOME: /home/runner/.android
|
||||||
|
ANDROID_EMULATOR_HOME: /home/runner/.android
|
||||||
|
ANDROID_AVD_HOME: /home/runner/.android/avd
|
||||||
|
AVD_CONFIG_PATH: "~/.android/avd/test.avd/config.ini"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: actions/download-artifact@v7
|
||||||
|
with:
|
||||||
|
name: debug-apk-x64
|
||||||
|
path: .
|
||||||
|
- uses: ./.github/actions/free_up_space
|
||||||
|
# https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/
|
||||||
|
- name: Enable KVM group perms
|
||||||
|
run: |
|
||||||
|
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger --name-match=kvm
|
||||||
|
- name: AVD cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
id: avd-cache
|
||||||
|
with:
|
||||||
|
path: ~/.android/*
|
||||||
|
key: avd-${{ matrix.api-level }}-integration_docker
|
||||||
|
- name: create AVD and generate snapshot for caching
|
||||||
|
if: steps.avd-cache.outputs.cache-hit != 'true'
|
||||||
|
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b
|
||||||
|
with:
|
||||||
|
api-level: ${{ matrix.api-level }}
|
||||||
|
target: google_apis
|
||||||
|
arch: x86_64
|
||||||
|
cores: 16
|
||||||
|
ndk: 28.2.13676358
|
||||||
|
force-avd-creation: false
|
||||||
|
disk-size: 4096M
|
||||||
|
ram-size: 4096M
|
||||||
|
sdcard-path-or-size: 4096M
|
||||||
|
emulator-options: -no-window -wipe-data -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||||
|
script: |
|
||||||
|
cat ${{ env.AVD_CONFIG_PATH }}
|
||||||
|
|
||||||
|
sed -i.bak 's/hw.lcd.density = .*/hw.lcd.density=420/' ${{ env.AVD_CONFIG_PATH }}
|
||||||
|
sed -i.bak 's/hw.lcd.height = .*/hw.lcd.height=1920/' ${{ env.AVD_CONFIG_PATH }}
|
||||||
|
sed -i.bak 's/hw.lcd.width = .*/hw.lcd.width=1080/' ${{ env.AVD_CONFIG_PATH }}
|
||||||
|
|
||||||
|
if ! grep -q "hw.lcd.density" ${{ env.AVD_CONFIG_PATH }} && echo "hw.lcd.density = 420" >> ${{ env.AVD_CONFIG_PATH }}; then :; fi
|
||||||
|
if ! grep -q "hw.lcd.height" ${{ env.AVD_CONFIG_PATH }} && echo "hw.lcd.height = 1920" >> ${{ env.AVD_CONFIG_PATH }}; then :; fi
|
||||||
|
if ! grep -q "hw.lcd.width" ${{ env.AVD_CONFIG_PATH }} && echo "hw.lcd.width = 1080" >> ${{ env.AVD_CONFIG_PATH }}; then :; fi
|
||||||
|
|
||||||
|
echo "Emulator settings (${{ env.AVD_CONFIG_PATH }})"
|
||||||
|
cat ${{ env.AVD_CONFIG_PATH }}
|
||||||
|
echo "Generated AVD snapshot for caching."
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
cache: true
|
||||||
|
- uses: remarkablemark/setup-maestro-cli@v1
|
||||||
|
- name: Load integration test env
|
||||||
|
run: cat integration_test/data/integration_users.env >> $GITHUB_ENV
|
||||||
|
- name: Prepare Homeserver
|
||||||
|
run: |
|
||||||
|
docker run -d --name synapse --tmpfs /data \
|
||||||
|
--volume="$(pwd)/integration_test/synapse/data/homeserver.yaml":/data/homeserver.yaml:rw \
|
||||||
|
--volume="$(pwd)/integration_test/synapse/data/localhost.log.config":/data/localhost.log.config:rw \
|
||||||
|
-p 80:80 matrixdotorg/synapse:latest
|
||||||
|
while ! curl -XGET "http://$HOMESERVER/_matrix/client/v3/login" >/dev/null 2>/dev/null; do
|
||||||
|
echo "Waiting for homeserver to be available... (GET http://$HOMESERVER/_matrix/client/v3/login)"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Homeserver is online!"
|
||||||
|
|
||||||
|
# create users
|
||||||
|
curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER1_NAME\", \"password\":\"$USER1_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register"
|
||||||
|
curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER2_NAME\", \"password\":\"$USER2_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register"
|
||||||
|
|
||||||
|
- name: Integration tests
|
||||||
|
id: integration_tests
|
||||||
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
|
with:
|
||||||
|
api-level: ${{ matrix.api-level }}
|
||||||
|
target: google_apis
|
||||||
|
arch: x86_64
|
||||||
|
cores: 16
|
||||||
|
ndk: 28.2.13676358
|
||||||
|
force-avd-creation: false
|
||||||
|
disk-size: 4096M
|
||||||
|
ram-size: 4096M
|
||||||
|
sdcard-path-or-size: 4096M
|
||||||
|
emulator-options: -no-snapshot-save -no-window -wipe-data -accel on -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||||
|
script: |
|
||||||
|
flutter run --use-application-binary=$PWD/app-debug.apk > flutter_logs.txt 2>&1 &
|
||||||
|
FLUTTER_PID=$!
|
||||||
|
sleep 10
|
||||||
|
maestro test integration_test/login.yaml --env HOMESERVER=10.0.2.2 --env USER1_NAME=${USER1_NAME} --env USER1_PW=${USER1_PW}
|
||||||
|
kill $FLUTTER_PID 2>/dev/null || true
|
||||||
|
cp flutter_logs.txt ~/.maestro/tests/
|
||||||
|
- name: Upload Flutter and Maestro logs
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: maestro-logs
|
||||||
|
path: ~/.maestro/tests
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
import 'package:fluffychat/pages/chat/chat_view.dart';
|
|
||||||
import 'package:fluffychat/pages/chat_list/chat_list_body.dart';
|
|
||||||
import 'package:fluffychat/pages/chat_list/search_title.dart';
|
|
||||||
import 'package:fluffychat/pages/invitation_selection/invitation_selection_view.dart';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:integration_test/integration_test.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/main.dart' as app;
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
import 'extensions/default_flows.dart';
|
|
||||||
import 'extensions/wait_for.dart';
|
|
||||||
import 'users.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
group('Integration Test', () {
|
|
||||||
setUpAll(() {
|
|
||||||
// this random dialog popping up is super hard to cover in tests
|
|
||||||
SharedPreferences.setMockInitialValues({
|
|
||||||
'chat.fluffy.show_no_google': false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Start app, login and logout', (WidgetTester tester) async {
|
|
||||||
app.main();
|
|
||||||
await tester.ensureAppStartedHomescreen();
|
|
||||||
await tester.ensureLoggedOut();
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Login again', (WidgetTester tester) async {
|
|
||||||
app.main();
|
|
||||||
await tester.ensureAppStartedHomescreen();
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Start chat and send message', (WidgetTester tester) async {
|
|
||||||
app.main();
|
|
||||||
await tester.ensureAppStartedHomescreen();
|
|
||||||
await tester.waitFor(find.byType(TextField));
|
|
||||||
await tester.enterText(find.byType(TextField), Users.user2.name);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.scrollUntilVisible(
|
|
||||||
find.text('Chats').first,
|
|
||||||
500,
|
|
||||||
scrollable: find
|
|
||||||
.descendant(
|
|
||||||
of: find.byType(ChatListViewBody),
|
|
||||||
matching: find.byType(Scrollable),
|
|
||||||
)
|
|
||||||
.first,
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.tap(find.text('Chats'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.waitFor(find.byType(SearchTitle));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.scrollUntilVisible(
|
|
||||||
find.text(Users.user2.name).first,
|
|
||||||
500,
|
|
||||||
scrollable: find
|
|
||||||
.descendant(
|
|
||||||
of: find.byType(ChatListViewBody),
|
|
||||||
matching: find.byType(Scrollable),
|
|
||||||
)
|
|
||||||
.first,
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.tap(find.text(Users.user2.name).first);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await tester.waitFor(
|
|
||||||
find.byType(ChatView),
|
|
||||||
timeout: const Duration(seconds: 5),
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
// in case the homeserver sends the username as search result
|
|
||||||
if (find.byIcon(Icons.send_outlined).evaluate().isNotEmpty) {
|
|
||||||
await tester.tap(find.byIcon(Icons.send_outlined));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await tester.waitFor(find.byType(ChatView));
|
|
||||||
await tester.enterText(find.byType(TextField).last, 'Test');
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
try {
|
|
||||||
await tester.waitFor(find.byIcon(Icons.send_outlined));
|
|
||||||
await tester.tap(find.byIcon(Icons.send_outlined));
|
|
||||||
} catch (_) {
|
|
||||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
||||||
}
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.waitFor(find.text('Test'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Spaces', (tester) async {
|
|
||||||
app.main();
|
|
||||||
await tester.ensureAppStartedHomescreen();
|
|
||||||
|
|
||||||
await tester.waitFor(find.byTooltip('Show menu'));
|
|
||||||
await tester.tap(find.byTooltip('Show menu'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.waitFor(find.byIcon(Icons.workspaces_outlined));
|
|
||||||
await tester.tap(find.byIcon(Icons.workspaces_outlined));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.waitFor(find.byType(TextField));
|
|
||||||
await tester.enterText(find.byType(TextField).last, 'Test Space');
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.waitFor(find.text('Invite contact'));
|
|
||||||
|
|
||||||
await tester.tap(find.text('Invite contact'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.waitFor(
|
|
||||||
find.descendant(
|
|
||||||
of: find.byType(InvitationSelectionView),
|
|
||||||
matching: find.byType(TextField),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.enterText(
|
|
||||||
find.descendant(
|
|
||||||
of: find.byType(InvitationSelectionView),
|
|
||||||
matching: find.byType(TextField),
|
|
||||||
),
|
|
||||||
Users.user2.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(milliseconds: 250));
|
|
||||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(milliseconds: 1000));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.tap(
|
|
||||||
find
|
|
||||||
.descendant(
|
|
||||||
of: find.descendant(
|
|
||||||
of: find.byType(InvitationSelectionView),
|
|
||||||
matching: find.byType(ListTile),
|
|
||||||
),
|
|
||||||
matching: find.text(Users.user2.name),
|
|
||||||
)
|
|
||||||
.last,
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.waitFor(find.maybeUppercaseText('Yes'));
|
|
||||||
await tester.tap(find.maybeUppercaseText('Yes'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.tap(find.byTooltip('Back'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.waitFor(find.text('Load 2 more participants'));
|
|
||||||
await tester.tap(find.text('Load 2 more participants'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text(Users.user2.name), findsOneWidget);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
5
integration_test/data/integration_users.env
Normal file
5
integration_test/data/integration_users.env
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
HOMESERVER=localhost
|
||||||
|
USER1_NAME=alice
|
||||||
|
USER1_PW=AliceInWonderland
|
||||||
|
USER2_NAME=bob
|
||||||
|
USER2_PW=JoWirSchaffenDas
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:fluffychat/pages/chat_list/chat_list_body.dart';
|
|
||||||
import 'package:fluffychat/pages/intro/intro_page.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import '../users.dart';
|
|
||||||
import 'wait_for.dart';
|
|
||||||
|
|
||||||
extension DefaultFlowExtensions on WidgetTester {
|
|
||||||
Future<void> login() async {
|
|
||||||
final tester = this;
|
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.waitFor(find.text('Let\'s start'));
|
|
||||||
|
|
||||||
expect(find.text('Let\'s start'), findsOneWidget);
|
|
||||||
|
|
||||||
final input = find.byType(TextField);
|
|
||||||
|
|
||||||
expect(input, findsOneWidget);
|
|
||||||
|
|
||||||
// getting the placeholder in place
|
|
||||||
await tester.tap(find.byIcon(Icons.search));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.enterText(input, homeserver);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// in case registration is allowed
|
|
||||||
// try {
|
|
||||||
await Future.delayed(const Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
await tester.scrollUntilVisible(
|
|
||||||
find.text('Login'),
|
|
||||||
500,
|
|
||||||
scrollable: find.descendant(
|
|
||||||
of: find.byKey(const Key('ConnectPageListView')),
|
|
||||||
matching: find.byType(Scrollable).first,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.tap(find.text('Login'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
/*} catch (e) {
|
|
||||||
log('Registration is not allowed. Proceeding with login...');
|
|
||||||
}*/
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await Future.delayed(const Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
final inputs = find.byType(TextField);
|
|
||||||
|
|
||||||
await tester.enterText(inputs.first, Users.user1.name);
|
|
||||||
await tester.enterText(inputs.last, Users.user1.password);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// pumpAndSettle does not work in here as setState is called
|
|
||||||
// asynchronously
|
|
||||||
await tester.waitFor(
|
|
||||||
find.byType(LinearProgressIndicator),
|
|
||||||
timeout: const Duration(milliseconds: 1500),
|
|
||||||
skipPumpAndSettle: true,
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
// in case the input action does not work on the desired platform
|
|
||||||
if (find.text('Login').evaluate().isNotEmpty) {
|
|
||||||
await tester.tap(find.text('Login'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
} catch (_) {
|
|
||||||
// may fail because of ongoing animation below dialog
|
|
||||||
}
|
|
||||||
|
|
||||||
await tester.waitFor(
|
|
||||||
find.byType(ChatListViewBody),
|
|
||||||
skipPumpAndSettle: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ensure PushProvider check passes
|
|
||||||
Future<void> acceptPushWarning() async {
|
|
||||||
final tester = this;
|
|
||||||
|
|
||||||
final matcher = find.maybeUppercaseText('Do not show again');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await tester.waitFor(matcher, timeout: const Duration(seconds: 5));
|
|
||||||
|
|
||||||
// the FCM push error dialog to be handled...
|
|
||||||
await tester.tap(matcher);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> ensureLoggedOut() async {
|
|
||||||
final tester = this;
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
if (find.byType(ChatListViewBody).evaluate().isNotEmpty) {
|
|
||||||
await tester.tap(find.byTooltip('Show menu'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.tap(find.text('Settings'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.scrollUntilVisible(
|
|
||||||
find.text('Account'),
|
|
||||||
500,
|
|
||||||
scrollable: find.descendant(
|
|
||||||
of: find.byKey(const Key('SettingsListViewContent')),
|
|
||||||
matching: find.byType(Scrollable),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.tap(find.text('Logout'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.tap(find.maybeUppercaseText('Yes'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> ensureAppStartedHomescreen({
|
|
||||||
Duration timeout = const Duration(seconds: 20),
|
|
||||||
}) async {
|
|
||||||
final tester = this;
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
final homeserverPickerFinder = find.byType(IntroPage);
|
|
||||||
final chatListFinder = find.byType(ChatListViewBody);
|
|
||||||
|
|
||||||
final end = DateTime.now().add(timeout);
|
|
||||||
|
|
||||||
log(
|
|
||||||
'Waiting for HomeserverPicker or ChatListViewBody...',
|
|
||||||
name: 'Test Runner',
|
|
||||||
);
|
|
||||||
do {
|
|
||||||
if (DateTime.now().isAfter(end)) {
|
|
||||||
throw Exception(
|
|
||||||
'Timed out waiting for HomeserverPicker or ChatListViewBody',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await pumpAndSettle();
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
} while (homeserverPickerFinder.evaluate().isEmpty &&
|
|
||||||
chatListFinder.evaluate().isEmpty);
|
|
||||||
|
|
||||||
if (homeserverPickerFinder.evaluate().isNotEmpty) {
|
|
||||||
log('Found HomeserverPicker, performing login.', name: 'Test Runner');
|
|
||||||
await tester.login();
|
|
||||||
} else {
|
|
||||||
log('Found ChatListViewBody, skipping login.', name: 'Test Runner');
|
|
||||||
}
|
|
||||||
|
|
||||||
await tester.acceptPushWarning();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
/// Workaround for https://github.com/flutter/flutter/issues/88765
|
|
||||||
extension WaitForExtension on WidgetTester {
|
|
||||||
Future<void> waitFor(
|
|
||||||
Finder finder, {
|
|
||||||
Duration timeout = const Duration(seconds: 20),
|
|
||||||
bool skipPumpAndSettle = false,
|
|
||||||
}) async {
|
|
||||||
final end = DateTime.now().add(timeout);
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (DateTime.now().isAfter(end)) {
|
|
||||||
throw Exception('Timed out waiting for $finder');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!skipPumpAndSettle) {
|
|
||||||
await pumpAndSettle();
|
|
||||||
}
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
} while (finder.evaluate().isEmpty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension MaybeUppercaseFinder on CommonFinders {
|
|
||||||
/// On Android some button labels are in uppercase while on iOS they
|
|
||||||
/// are not. This method tries both.
|
|
||||||
Finder maybeUppercaseText(
|
|
||||||
String text, {
|
|
||||||
bool findRichText = false,
|
|
||||||
bool skipOffstage = true,
|
|
||||||
}) {
|
|
||||||
try {
|
|
||||||
final finder = find.text(
|
|
||||||
text.toUpperCase(),
|
|
||||||
findRichText: findRichText,
|
|
||||||
skipOffstage: skipOffstage,
|
|
||||||
);
|
|
||||||
expect(finder, findsOneWidget);
|
|
||||||
return finder;
|
|
||||||
} catch (_) {
|
|
||||||
return find.text(
|
|
||||||
text,
|
|
||||||
findRichText: findRichText,
|
|
||||||
skipOffstage: skipOffstage,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
integration_test/login.yaml
Normal file
31
integration_test/login.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
appId: chat.fluffy.fluffychat
|
||||||
|
---
|
||||||
|
- assertVisible: "Sign in"
|
||||||
|
- tapOn: "Sign in"
|
||||||
|
- tapOn: "Search or enter homeserver address"
|
||||||
|
- inputText: "http://${HOMESERVER}"
|
||||||
|
- pressKey: "back"
|
||||||
|
- tapOn:
|
||||||
|
id: "homeserver_tile_0"
|
||||||
|
- tapOn:
|
||||||
|
id: "connect_to_homeserver_button"
|
||||||
|
- assertVisible: "Log in to http://${HOMESERVER}"
|
||||||
|
- inputText: "${USER1_NAME}"
|
||||||
|
- tapOn: "Password"
|
||||||
|
- inputText: "${USER1_PW}"
|
||||||
|
- tapOn: "Login" # Click the login button
|
||||||
|
- tapOn:
|
||||||
|
id: "store_in_secure_storage"
|
||||||
|
- tapOn: "Next"
|
||||||
|
- tapOn:
|
||||||
|
text: "Close"
|
||||||
|
index: 1
|
||||||
|
- assertVisible: "Push notifications not available"
|
||||||
|
- tapOn: "Do not show again"
|
||||||
|
- tapOn:
|
||||||
|
id: "accounts_and_settings" # Open the popup menu
|
||||||
|
- tapOn: "Settings"
|
||||||
|
- scrollUntilVisible:
|
||||||
|
element: "Logout"
|
||||||
|
- tapOn: "Logout"
|
||||||
|
- tapOn: "Logout" # Confirm logout dialog
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
abstract class Users {
|
|
||||||
const Users._();
|
|
||||||
|
|
||||||
static const user1 = User(
|
|
||||||
String.fromEnvironment(
|
|
||||||
'USER1_NAME',
|
|
||||||
defaultValue: 'alice',
|
|
||||||
),
|
|
||||||
String.fromEnvironment(
|
|
||||||
'USER1_PW',
|
|
||||||
defaultValue: 'AliceInWonderland',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
static const user2 = User(
|
|
||||||
String.fromEnvironment(
|
|
||||||
'USER2_NAME',
|
|
||||||
defaultValue: 'bob',
|
|
||||||
),
|
|
||||||
String.fromEnvironment(
|
|
||||||
'USER2_PW',
|
|
||||||
defaultValue: 'JoWirSchaffenDas',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class User {
|
|
||||||
final String name;
|
|
||||||
final String password;
|
|
||||||
|
|
||||||
const User(this.name, this.password);
|
|
||||||
}
|
|
||||||
|
|
||||||
const homeserver = 'http://${String.fromEnvironment(
|
|
||||||
'HOMESERVER',
|
|
||||||
defaultValue: 'localhost',
|
|
||||||
)}';
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/semantics.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
@ -102,6 +103,9 @@ Future<void> startGui(List<Client> clients, SharedPreferences store) async {
|
||||||
await firstClient?.accountDataLoading;
|
await firstClient?.accountDataLoading;
|
||||||
|
|
||||||
runApp(FluffyChatApp(clients: clients, pincode: pin, store: store));
|
runApp(FluffyChatApp(clients: clients, pincode: pin, store: store));
|
||||||
|
if (const String.fromEnvironment('WITH_SEMANTICS') == 'true') {
|
||||||
|
SemanticsBinding.instance.ensureSemantics();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Watches the lifecycle changes to start the application when it
|
/// Watches the lifecycle changes to start the application when it
|
||||||
|
|
|
||||||
|
|
@ -204,8 +204,12 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (_supportsSecureStorage)
|
if (_supportsSecureStorage)
|
||||||
CheckboxListTile.adaptive(
|
Semantics(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8.0),
|
identifier: 'store_in_secure_storage',
|
||||||
|
child: CheckboxListTile.adaptive(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
),
|
||||||
value: _storeInSecureStorage,
|
value: _storeInSecureStorage,
|
||||||
activeColor: theme.colorScheme.primary,
|
activeColor: theme.colorScheme.primary,
|
||||||
onChanged: (b) {
|
onChanged: (b) {
|
||||||
|
|
@ -218,8 +222,11 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
L10n.of(context).storeInSecureStorageDescription,
|
L10n.of(context).storeInSecureStorageDescription,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
CheckboxListTile.adaptive(
|
Semantics(
|
||||||
|
identifier: 'copy_to_clipboard',
|
||||||
|
child: CheckboxListTile.adaptive(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 8.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
value: _recoveryKeyCopied,
|
value: _recoveryKeyCopied,
|
||||||
activeColor: theme.colorScheme.primary,
|
activeColor: theme.colorScheme.primary,
|
||||||
|
|
@ -230,6 +237,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
title: Text(L10n.of(context).copyToClipboard),
|
title: Text(L10n.of(context).copyToClipboard),
|
||||||
subtitle: Text(L10n.of(context).saveKeyManuallyDescription),
|
subtitle: Text(L10n.of(context).saveKeyManuallyDescription),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: const Icon(Icons.check_outlined),
|
icon: const Icon(Icons.check_outlined),
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,10 @@ class ClientChooserButton extends StatelessWidget {
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
borderRadius: BorderRadius.circular(99),
|
borderRadius: BorderRadius.circular(99),
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
child: Semantics(
|
||||||
|
identifier: 'accounts_and_settings',
|
||||||
child: PopupMenuButton<Object>(
|
child: PopupMenuButton<Object>(
|
||||||
|
tooltip: 'Accounts and settings',
|
||||||
popUpAnimationStyle: FluffyThemes.isColumnMode(context)
|
popUpAnimationStyle: FluffyThemes.isColumnMode(context)
|
||||||
? AnimationStyle.noAnimation
|
? AnimationStyle.noAnimation
|
||||||
: null, // https://github.com/flutter/flutter/issues/167180
|
: null, // https://github.com/flutter/flutter/issues/167180
|
||||||
|
|
@ -185,12 +188,14 @@ class ClientChooserButton extends StatelessWidget {
|
||||||
child: Avatar(
|
child: Avatar(
|
||||||
mxContent: snapshot.data?.avatarUrl,
|
mxContent: snapshot.data?.avatarUrl,
|
||||||
name:
|
name:
|
||||||
snapshot.data?.displayName ?? matrix.client.userID?.localpart,
|
snapshot.data?.displayName ??
|
||||||
|
matrix.client.userID?.localpart,
|
||||||
size: 32,
|
size: 32,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ class SettingsController extends State<Settings> {
|
||||||
context: context,
|
context: context,
|
||||||
future: () => matrix.client.logout(),
|
future: () => matrix.client.logout(),
|
||||||
);
|
);
|
||||||
|
context.go('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setAvatarAction() async {
|
Future<void> setAvatarAction() async {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,9 @@ class SignInPage extends StatelessWidget {
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
final server = publicHomeservers[i];
|
final server = publicHomeservers[i];
|
||||||
final website = server.website;
|
final website = server.website;
|
||||||
return RadioListTile.adaptive(
|
return Semantics(
|
||||||
|
identifier: 'homeserver_tile_$i',
|
||||||
|
child: RadioListTile.adaptive(
|
||||||
value: server,
|
value: server,
|
||||||
enabled:
|
enabled:
|
||||||
state.loginLoading.connectionState !=
|
state.loginLoading.connectionState !=
|
||||||
|
|
@ -140,7 +142,8 @@ class SignInPage extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
...?server.languages?.map(
|
...?server.languages?.map(
|
||||||
(language) => Material(
|
(language) => Material(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius:
|
||||||
|
BorderRadius.circular(
|
||||||
AppConfig.borderRadius,
|
AppConfig.borderRadius,
|
||||||
),
|
),
|
||||||
color: theme
|
color: theme
|
||||||
|
|
@ -166,7 +169,8 @@ class SignInPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
...server.features!.map(
|
...server.features!.map(
|
||||||
(feature) => Material(
|
(feature) => Material(
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius:
|
||||||
|
BorderRadius.circular(
|
||||||
AppConfig.borderRadius,
|
AppConfig.borderRadius,
|
||||||
),
|
),
|
||||||
color: theme
|
color: theme
|
||||||
|
|
@ -193,10 +197,12 @@ class SignInPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
server.description ?? 'A matrix homeserver',
|
server.description ??
|
||||||
|
'A matrix homeserver',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -219,6 +225,8 @@ class SignInPage extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
|
child: Semantics(
|
||||||
|
identifier: 'connect_to_homeserver_button',
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: theme.colorScheme.primary,
|
backgroundColor: theme.colorScheme.primary,
|
||||||
|
|
@ -244,6 +252,7 @@ class SignInPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,13 @@ class SignInViewModel extends ValueNotifier<SignInState> {
|
||||||
)
|
)
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[];
|
[];
|
||||||
final splitted = filterText.split('.');
|
if (Uri.tryParse(filterText) != null &&
|
||||||
if (splitted.length >= 2 && !splitted.any((part) => part.isEmpty)) {
|
!filteredPublicHomeservers.any(
|
||||||
if (!filteredPublicHomeservers.any(
|
|
||||||
(homeserver) => homeserver.name == filterText,
|
(homeserver) => homeserver.name == filterText,
|
||||||
)) {
|
)) {
|
||||||
filteredPublicHomeservers.add(PublicHomeserverData(name: filterText));
|
filteredPublicHomeservers.add(PublicHomeserverData(name: filterText));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
value = value.copyWith(
|
value = value.copyWith(
|
||||||
filteredPublicHomeservers: filteredPublicHomeservers,
|
filteredPublicHomeservers: filteredPublicHomeservers,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
export USER1_NAME="alice"
|
|
||||||
export USER1_PW="AliceInWonderland"
|
|
||||||
export USER2_NAME="bob"
|
|
||||||
export USER2_PW="JoWirSchaffenDas"
|
|
||||||
|
|
@ -1,60 +1,11 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
if [ -z $HOMESERVER ]; then
|
|
||||||
echo "Please ensure HOMESERVER environment variable is set to the IP or hostname of the homeserver."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z $USER1_NAME ]; then
|
|
||||||
echo "Please ensure USER1_NAME environment variable is set to first user name."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z $USER1_PW ]; then
|
|
||||||
echo "Please ensure USER1_PW environment variable is set to first user password."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z $USER2_NAME ]; then
|
|
||||||
echo "Please ensure USER2_NAME environment variable is set to second user name."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ -z $USER2_PW ]; then
|
|
||||||
echo "Please ensure USER2_PW environment variable is set to second user password."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Waiting for homeserver to be available... (GET http://$HOMESERVER/_matrix/client/v3/login)"
|
while ! curl -XGET "http://localhost/_matrix/client/v3/login" >/dev/null 2>/dev/null; do
|
||||||
|
echo "Waiting for homeserver to be available... (GET http://localhost/_matrix/client/v3/login)"
|
||||||
while ! curl -XGET "http://$HOMESERVER/_matrix/client/v3/login" >/dev/null 2>/dev/null; do
|
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Homeserver is up."
|
|
||||||
|
|
||||||
# create users
|
# create users
|
||||||
|
curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER1_NAME\", \"password\":\"$USER1_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://localhost/_matrix/client/r0/register"
|
||||||
curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER1_NAME\", \"password\":\"$USER1_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register"
|
curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER2_NAME\", \"password\":\"$USER2_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://localhost/_matrix/client/r0/register"
|
||||||
curl -fS --retry 3 -XPOST -d "{\"username\":\"$USER2_NAME\", \"password\":\"$USER2_PW\", \"inhibit_login\":true, \"auth\": {\"type\":\"m.login.dummy\"}}" "http://$HOMESERVER/_matrix/client/r0/register"
|
|
||||||
|
|
||||||
usertoken1=$(curl -fS --retry 3 "http://$HOMESERVER/_matrix/client/r0/login" -H "Content-Type: application/json" -d "{\"type\": \"m.login.password\", \"identifier\": {\"type\": \"m.id.user\",\"user\": \"$USER1_NAME\"},\"password\":\"$USER1_PW\"}" | jq -r '.access_token')
|
|
||||||
usertoken2=$(curl -fS --retry 3 "http://$HOMESERVER/_matrix/client/r0/login" -H "Content-Type: application/json" -d "{\"type\": \"m.login.password\", \"identifier\": {\"type\": \"m.id.user\",\"user\": \"$USER2_NAME\"},\"password\":\"$USER2_PW\"}" | jq -r '.access_token')
|
|
||||||
|
|
||||||
|
|
||||||
# get usernames' mxids
|
|
||||||
mxid1=$(curl -fS --retry 3 "http://$HOMESERVER/_matrix/client/r0/account/whoami" -H "Authorization: Bearer $usertoken1" | jq -r .user_id)
|
|
||||||
mxid2=$(curl -fS --retry 3 "http://$HOMESERVER/_matrix/client/r0/account/whoami" -H "Authorization: Bearer $usertoken2" | jq -r .user_id)
|
|
||||||
|
|
||||||
# setting the display name to username
|
|
||||||
curl -fS --retry 3 -XPUT -d "{\"displayname\":\"$USER1_NAME\"}" "http://$HOMESERVER/_matrix/client/v3/profile/$mxid1/displayname" -H "Authorization: Bearer $usertoken1"
|
|
||||||
curl -fS --retry 3 -XPUT -d "{\"displayname\":\"$USER2_NAME\"}" "http://$HOMESERVER/_matrix/client/v3/profile/$mxid2/displayname" -H "Authorization: Bearer $usertoken2"
|
|
||||||
|
|
||||||
echo "Set display names"
|
|
||||||
|
|
||||||
# create new room to invite user too
|
|
||||||
roomID=$(curl --retry 3 --silent --fail -XPOST -d "{\"name\":\"$USER2_NAME\", \"is_direct\": true}" "http://$HOMESERVER/_matrix/client/r0/createRoom?access_token=$usertoken2" | jq -r '.room_id')
|
|
||||||
echo "Created room '$roomID'"
|
|
||||||
|
|
||||||
# send message in created room
|
|
||||||
curl --retry 3 --fail --silent -XPOST -d '{"msgtype":"m.text", "body":"joined room successfully"}' "http://$HOMESERVER/_matrix/client/r0/rooms/$roomID/send/m.room.message?access_token=$usertoken2"
|
|
||||||
echo "Sent message"
|
|
||||||
|
|
||||||
curl -fS --retry 3 -XPOST -d "{\"user_id\":\"$mxid1\"}" "http://$HOMESERVER/_matrix/client/r0/rooms/$roomID/invite?access_token=$usertoken2"
|
|
||||||
echo "Invited $USER1_NAME"
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue