diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d78c1ba7b0..0ed2eeec55 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,7 +48,8 @@ jobs: AppleSwift56, Swift56, WebChromium, - WebNode + WebNode, + ReactNative ] steps: diff --git a/templates/react-native/src/client.ts.twig b/templates/react-native/src/client.ts.twig index f4bdb85a2a..00a366d052 100644 --- a/templates/react-native/src/client.ts.twig +++ b/templates/react-native/src/client.ts.twig @@ -543,6 +543,10 @@ class Client { } } + async ping(): Promise { + return this.call('GET', new URL(this.config.endpoint + '/ping')); + } + async call(method: string, url: URL, headers: Headers = {}, params: Payload = {}, responseType = 'json'): Promise { method = method.toUpperCase(); diff --git a/templates/react-native/src/models.ts.twig b/templates/react-native/src/models.ts.twig index a8a14ade39..dcb30e7137 100644 --- a/templates/react-native/src/models.ts.twig +++ b/templates/react-native/src/models.ts.twig @@ -28,4 +28,25 @@ export namespace Models { }; {% endif %} {% endfor %} +{% for requestModel in spec.requestModels %} + + /** + * {{ requestModel.description }} + */ + export type {{ requestModel.name | caseUcfirst }}{{ requestModel.name | getGenerics(spec, true) | raw }} = { +{% for property in requestModel.properties %} + /** + * {{ property.description | raw }} + */ + {{ property.name | removeDash }}{% if not property.required %}?{% endif %}: {{ property | getSubSchema(spec, requestModel.name) | raw }}; +{% endfor %} + } +{% if requestModel.additionalProperties %} + + export type Default{{ requestModel.name | caseUcfirst }}{{ requestModel.name | getGenerics(spec, true) | raw }} = {{ requestModel.name | caseUcfirst }}{{ requestModel.name | getGenerics(spec, true) | raw }} & { + [key: string]: any; + [__default]: true; + }; +{% endif %} +{% endfor %} } diff --git a/templates/react-native/src/services/template.ts.twig b/templates/react-native/src/services/template.ts.twig index 40e3be45ab..4e99ba6bc3 100644 --- a/templates/react-native/src/services/template.ts.twig +++ b/templates/react-native/src/services/template.ts.twig @@ -45,7 +45,7 @@ export class {{ service.name | caseUcfirst }} extends Service { {%~ endif %} */ {%~ if method.parameters.all|length > 0 %} - {% if method.type == 'upload'%}async {% endif %}{{ method.name | caseCamel }}{{ method.responseModel | getGenerics(spec) | raw }}(params{% set hasRequiredParams = false %}{% for parameter in method.parameters.all %}{% if parameter.required %}{% set hasRequiredParams = true %}{% endif %}{% endfor %}{% if not hasRequiredParams %}?{% endif %}: { {% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | getPropertyType(method) | raw }}{% if not loop.last %}, {% endif %}{% endfor %} {% if 'multipart/form-data' in method.consumes %}, onProgress?: (progress: UploadProgress) => void{% endif %} }): {{ method | getReturn(spec) | raw }}; + {% if 'multipart/form-data' in method.consumes %}async {% endif %}{{ method.name | caseCamel }}{{ method.responseModel | getGenerics(spec) | raw }}(params{% set hasRequiredParams = false %}{% for parameter in method.parameters.all %}{% if parameter.required %}{% set hasRequiredParams = true %}{% endif %}{% endfor %}{% if not hasRequiredParams %}?{% endif %}: { {% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | getPropertyType(method) | raw }}{% if not loop.last %}, {% endif %}{% endfor %} {% if 'multipart/form-data' in method.consumes %}, onProgress?: (progress: UploadProgress) => void{% endif %} }): {{ method | getReturn(spec) | raw }}; /** {%~ if method.description %} * {{ method.description | replace({'\n': '\n * '}) | raw }} @@ -58,8 +58,8 @@ export class {{ service.name | caseUcfirst }} extends Service { * @returns {{ '{' }}{{ method | getReturn(spec) | raw }}{{ '}' }} * @deprecated Use the object parameter style method for a better developer experience. */ - {% if method.type == 'upload'%}async {% endif %}{{ method.name | caseCamel }}{{ method.responseModel | getGenerics(spec) | raw }}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | getPropertyType(method) | raw }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, onProgress?: (progress: UploadProgress) => void{% endif %}): {{ method | getReturn(spec) | raw }}; - {% if method.type == 'upload'%}async {% endif %}{{ method.name | caseCamel }}{{ method.responseModel | getGenerics(spec) | raw }}( + {% if 'multipart/form-data' in method.consumes %}async {% endif %}{{ method.name | caseCamel }}{{ method.responseModel | getGenerics(spec) | raw }}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | getPropertyType(method) | raw }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, onProgress?: (progress: UploadProgress) => void{% endif %}): {{ method | getReturn(spec) | raw }}; + {% if 'multipart/form-data' in method.consumes %}async {% endif %}{{ method.name | caseCamel }}{{ method.responseModel | getGenerics(spec) | raw }}( {% if method.parameters.all|length > 0 %}paramsOrFirst{% if not method.parameters.all[0].required or method.parameters.all[0].nullable %}?{% endif %}: { {% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | getPropertyType(method) | raw }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, onProgress?: (progress: UploadProgress) => void {% endif %} } | {{ method.parameters.all[0] | getPropertyType(method) | raw }}{% if method.parameters.all|length > 1 %}, ...rest: [{% for parameter in method.parameters.all[1:] %}({{ parameter | getPropertyType(method) | raw }})?{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %},((progress: UploadProgress) => void)?{% endif %}]{% endif %}{% endif %} diff --git a/templates/web/src/services/realtime.ts.twig b/templates/web/src/services/realtime.ts.twig index ec7d3cf593..635a859a50 100644 --- a/templates/web/src/services/realtime.ts.twig +++ b/templates/web/src/services/realtime.ts.twig @@ -183,11 +183,13 @@ export class Realtime { const connectionId = ++this.connectionId; {% if language.name == 'ReactNative' %} const WebSocketCtor: any = WebSocket; - const socket = (this.socket = new WebSocketCtor(url, undefined, { - headers: { - Origin: `{{ spec.title | caseLower }}-${Platform.OS}://${this.client.config.platform}` - } - })); + const socket = (this.socket = Platform.OS === 'web' + ? new WebSocketCtor(url) + : new WebSocketCtor(url, undefined, { + headers: { + Origin: `{{ spec.title | caseLower }}-${Platform.OS}://${this.client.config.platform}` + } + })); {% else %} const socket = (this.socket = new WebSocket(url)); {% endif %} diff --git a/tests/ReactNativeTest.php b/tests/ReactNativeTest.php new file mode 100644 index 0000000000..bb7987622b --- /dev/null +++ b/tests/ReactNativeTest.php @@ -0,0 +1,45 @@ + { + let response; + let responseRealtime = 'Realtime failed!'; + let responseRealtimeWithQueries = 'Realtime failed!'; + let responseRealtimeWithQueriesFailure = 'Realtime failed!'; + + let rtsubFailureUnsubscribed = false; + let rtsubFailureFiredAfterUnsubscribe = false; + + const client = new Client(); + + const foo = new Foo(client); + const bar = new Bar(client); + const general = new General(client); + const sdkHeaders = client.getHeaders(); + + console.log( + `x-sdk-name: ${sdkHeaders['x-sdk-name']}; x-sdk-platform: ${sdkHeaders['x-sdk-platform']}; x-sdk-language: ${sdkHeaders['x-sdk-language']}; x-sdk-version: ${sdkHeaders['x-sdk-version']}` + ); + + client.setProject('123456'); + response = await client.ping(); + console.log(response.result); + + client.setProject('console'); + client.setEndpointRealtime('wss://cloud.appwrite.io/v1'); + + const realtime = new Realtime(client); + + await realtime.subscribe(['tests'], (message) => { + responseRealtime = message.payload.response; + }); + + const rtsubWithQueries = await realtime.subscribe( + ['tests'], + (message) => { + responseRealtimeWithQueries = message.payload.response; + }, + [[Query.equal('response', ['WS:/v1/realtime:passed'])]], + ); + + const rtsubWithQueriesFailure = await realtime.subscribe( + ['tests'], + (message) => { + if (rtsubFailureUnsubscribed) rtsubFailureFiredAfterUnsubscribe = true; + responseRealtimeWithQueriesFailure = message.payload.response; + }, + [[Query.equal('response', ['failed'])]], + ); + + // Foo + response = await foo.get('string', 123, ['string in array']); + console.log(response.result); + response = await foo.post('string', 123, ['string in array']); + console.log(response.result); + response = await foo.put('string', 123, ['string in array']); + console.log(response.result); + response = await foo.patch('string', 123, ['string in array']); + console.log(response.result); + response = await foo.delete('string', 123, ['string in array']); + console.log(response.result); + + // Foo (Object params) + response = await foo.get({ x: 'string', y: 123, z: ['string in array'] }); + console.log(response.result); + response = await foo.post({ x: 'string', y: 123, z: ['string in array'] }); + console.log(response.result); + response = await foo.put({ x: 'string', y: 123, z: ['string in array'] }); + console.log(response.result); + response = await foo.patch({ x: 'string', y: 123, z: ['string in array'] }); + console.log(response.result); + response = await foo.delete({ x: 'string', y: 123, z: ['string in array'] }); + console.log(response.result); + + // Bar + response = await bar.get('string', 123, ['string in array']); + console.log(response.result); + response = await bar.post('string', 123, ['string in array']); + console.log(response.result); + response = await bar.put('string', 123, ['string in array']); + console.log(response.result); + response = await bar.patch('string', 123, ['string in array']); + console.log(response.result); + response = await bar.delete('string', 123, ['string in array']); + console.log(response.result); + + // Bar (Object params) + response = await bar.get({ required: 'string', xdefault: 123, z: ['string in array'] }); + console.log(response.result); + response = await bar.post({ required: 'string', xdefault: 123, z: ['string in array'] }); + console.log(response.result); + response = await bar.put({ required: 'string', xdefault: 123, z: ['string in array'] }); + console.log(response.result); + response = await bar.patch({ required: 'string', xdefault: 123, z: ['string in array'] }); + console.log(response.result); + response = await bar.delete({ required: 'string', xdefault: 123, z: ['string in array'] }); + console.log(response.result); + + // General + response = await general.redirect(); + console.log(response.result); + + // Enum + response = await general.enum(MockType.First); + console.log(response.result); + + // Request model tests + response = await general.createPlayer({ id: 'player1', name: 'John Doe', score: 100 }); + console.log(response.result); + response = await general.createPlayers([ + { id: 'player1', name: 'John Doe', score: 100 }, + { id: 'player2', name: 'Jane Doe', score: 200 }, + ]); + console.log(response.result); + + // Exception responses + try { + response = await general.error400(); + } catch (error) { + console.log(error.message); + console.log(error.response); + } + try { + response = await general.error500(); + } catch (error) { + console.log(error.message); + console.log(error.response); + } + try { + response = await general.error502(); + } catch (error) { + console.log(error.message); + console.log(error.response); + } + try { + client.setEndpoint('htp://cloud.appwrite.io/v1'); + } catch (error) { + console.log(error.message); + } + + // Realtime waits + const delay = (ms) => new Promise((res) => setTimeout(res, ms)); + await delay(5000); + console.log(responseRealtime); + console.log(responseRealtimeWithQueries); + console.log(responseRealtimeWithQueriesFailure); + + try { + await rtsubWithQueriesFailure.unsubscribe(); + rtsubFailureUnsubscribed = true; + await rtsubWithQueriesFailure.unsubscribe(); + await delay(500); + if (rtsubFailureFiredAfterUnsubscribe) { + throw new Error('callback fired after unsubscribe'); + } + console.log('Realtime unsubscribe:passed'); + } catch (e) { + console.log('Realtime unsubscribe:failed'); + } + + try { + await rtsubWithQueries.update({ channels: ['tests'], queries: [] }); + console.log('Realtime update:passed'); + } catch (e) { + console.log('Realtime update:failed'); + } + + try { + await realtime.disconnect(); + console.log('Realtime disconnect:passed'); + } catch (e) { + console.log('Realtime disconnect:failed'); + } + + // Query helper tests + console.log(Query.equal('released', [true])); + console.log(Query.equal('title', ['Spiderman', 'Dr. Strange'])); + console.log(Query.notEqual('title', 'Spiderman')); + console.log(Query.lessThan('releasedYear', 1990)); + console.log(Query.greaterThan('releasedYear', 1990)); + console.log(Query.search('name', 'john')); + console.log(Query.isNull('name')); + console.log(Query.isNotNull('name')); + console.log(Query.between('age', 50, 100)); + console.log(Query.between('age', 50.5, 100.5)); + console.log(Query.between('name', 'Anna', 'Brad')); + console.log(Query.startsWith('name', 'Ann')); + console.log(Query.endsWith('name', 'nne')); + console.log(Query.select(['name', 'age'])); + console.log(Query.orderAsc('title')); + console.log(Query.orderDesc('title')); + console.log(Query.orderRandom()); + console.log(Query.cursorAfter('my_movie_id')); + console.log(Query.cursorBefore('my_movie_id')); + console.log(Query.limit(50)); + console.log(Query.offset(20)); + console.log(Query.contains('title', 'Spider')); + console.log(Query.contains('labels', 'first')); + console.log(Query.containsAny('labels', ['first', 'second'])); + console.log(Query.containsAll('labels', ['first', 'second'])); + + console.log(Query.notContains('title', 'Spider')); + console.log(Query.notSearch('name', 'john')); + console.log(Query.notBetween('age', 50, 100)); + console.log(Query.notStartsWith('name', 'Ann')); + console.log(Query.notEndsWith('name', 'nne')); + console.log(Query.createdBefore('2023-01-01')); + console.log(Query.createdAfter('2023-01-01')); + console.log(Query.createdBetween('2023-01-01', '2023-12-31')); + console.log(Query.updatedBefore('2023-01-01')); + console.log(Query.updatedAfter('2023-01-01')); + console.log(Query.updatedBetween('2023-01-01', '2023-12-31')); + + console.log(Query.distanceEqual('location', [[40.7128, -74], [40.7128, -74]], 1000)); + console.log(Query.distanceEqual('location', [40.7128, -74], 1000, true)); + console.log(Query.distanceNotEqual('location', [40.7128, -74], 1000)); + console.log(Query.distanceNotEqual('location', [40.7128, -74], 1000, true)); + console.log(Query.distanceGreaterThan('location', [40.7128, -74], 1000)); + console.log(Query.distanceGreaterThan('location', [40.7128, -74], 1000, true)); + console.log(Query.distanceLessThan('location', [40.7128, -74], 1000)); + console.log(Query.distanceLessThan('location', [40.7128, -74], 1000, true)); + + console.log(Query.intersects('location', [40.7128, -74])); + console.log(Query.notIntersects('location', [40.7128, -74])); + console.log(Query.crosses('location', [40.7128, -74])); + console.log(Query.notCrosses('location', [40.7128, -74])); + console.log(Query.overlaps('location', [40.7128, -74])); + console.log(Query.notOverlaps('location', [40.7128, -74])); + console.log(Query.touches('location', [40.7128, -74])); + console.log(Query.notTouches('location', [40.7128, -74])); + console.log(Query.contains('location', [[40.7128, -74], [40.7128, -74]])); + console.log(Query.notContains('location', [[40.7128, -74], [40.7128, -74]])); + console.log(Query.equal('location', [[40.7128, -74], [40.7128, -74]])); + console.log(Query.notEqual('location', [[40.7128, -74], [40.7128, -74]])); + + console.log(Query.or([ + Query.equal('released', true), + Query.lessThan('releasedYear', 1990), + ])); + console.log(Query.and([ + Query.equal('released', false), + Query.greaterThan('releasedYear', 2015), + ])); + + console.log(Query.regex('name', 'pattern.*')); + console.log(Query.exists(['attr1', 'attr2'])); + console.log(Query.notExists(['attr1', 'attr2'])); + console.log(Query.elemMatch('friends', [ + Query.equal('name', 'Alice'), + Query.greaterThan('age', 18), + ])); + + // Permission & Role helper tests + console.log(Permission.read(Role.any())); + console.log(Permission.write(Role.user(ID.custom('userid')))); + console.log(Permission.create(Role.users())); + console.log(Permission.update(Role.guests())); + console.log(Permission.delete(Role.team('teamId', 'owner'))); + console.log(Permission.delete(Role.team('teamId'))); + console.log(Permission.create(Role.member('memberId'))); + console.log(Permission.update(Role.users('verified'))); + console.log(Permission.update(Role.user(ID.custom('userid'), 'unverified'))); + console.log(Permission.create(Role.label('admin'))); + + // ID helper tests + console.log(ID.unique()); + console.log(ID.custom('custom_id')); + + // Channel helper tests + console.log(Channel.database('db1').collection('col1').document().toString()); + console.log(Channel.database('db1').collection('col1').document('doc1').toString()); + console.log(Channel.database('db1').collection('col1').document('doc1').create().toString()); + console.log(Channel.database('db1').collection('col1').document('doc1').upsert().toString()); + console.log(Channel.tablesdb('db1').table('table1').row().toString()); + console.log(Channel.tablesdb('db1').table('table1').row('row1').toString()); + console.log(Channel.tablesdb('db1').table('table1').row('row1').update().toString()); + console.log(Channel.account()); + console.log(Channel.bucket('bucket1').file().toString()); + console.log(Channel.bucket('bucket1').file('file1').toString()); + console.log(Channel.bucket('bucket1').file('file1').delete().toString()); + console.log(Channel.function('func2').toString()); + console.log(Channel.function('func1').toString()); + console.log(Channel.execution('exec2').toString()); + console.log(Channel.execution('exec1').toString()); + console.log(Channel.documents()); + console.log(Channel.rows()); + console.log(Channel.files()); + console.log(Channel.executions()); + console.log(Channel.teams()); + console.log(Channel.team('team2').toString()); + console.log(Channel.team('team1').toString()); + console.log(Channel.team('team1').create().toString()); + console.log(Channel.memberships()); + console.log(Channel.membership('membership2').toString()); + console.log(Channel.membership('membership1').toString()); + console.log(Channel.membership('membership1').update().toString()); + + // Operator helper tests + console.log(Operator.increment(1)); + console.log(Operator.increment(5, 100)); + console.log(Operator.decrement(1)); + console.log(Operator.decrement(3, 0)); + console.log(Operator.multiply(2)); + console.log(Operator.multiply(3, 1000)); + console.log(Operator.divide(2)); + console.log(Operator.divide(4, 1)); + console.log(Operator.modulo(5)); + console.log(Operator.power(2)); + console.log(Operator.power(3, 100)); + console.log(Operator.arrayAppend(['item1', 'item2'])); + console.log(Operator.arrayPrepend(['first', 'second'])); + console.log(Operator.arrayInsert(0, 'newItem')); + console.log(Operator.arrayRemove('oldItem')); + console.log(Operator.arrayUnique()); + console.log(Operator.arrayIntersect(['a', 'b', 'c'])); + console.log(Operator.arrayDiff(['x', 'y'])); + console.log(Operator.arrayFilter(Condition.Equal, 'test')); + console.log(Operator.stringConcat('suffix')); + console.log(Operator.stringReplace('old', 'new')); + console.log(Operator.toggle()); + console.log(Operator.dateAddDays(7)); + console.log(Operator.dateSubDays(3)); + console.log(Operator.dateSetNow()); + + window.__APPWRITE_TEST_DONE__ = true; +})().catch((err) => { + console.log('TEST RUNNER ERROR: ' + (err && err.message ? err.message : String(err))); + window.__APPWRITE_TEST_DONE__ = true; +}); diff --git a/tests/languages/react-native/index.html b/tests/languages/react-native/index.html new file mode 100644 index 0000000000..29d8a541ca --- /dev/null +++ b/tests/languages/react-native/index.html @@ -0,0 +1,11 @@ + + + + + + Appwrite React Native Test + + + + + diff --git a/tests/languages/react-native/rollup.test.config.mjs b/tests/languages/react-native/rollup.test.config.mjs new file mode 100644 index 0000000000..cc2e90bad7 --- /dev/null +++ b/tests/languages/react-native/rollup.test.config.mjs @@ -0,0 +1,57 @@ +import alias from '@rollup/plugin-alias'; +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import typescript from '@rollup/plugin-typescript'; +import replace from '@rollup/plugin-replace'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +export default { + input: 'browser.js', + output: { + file: 'dist/browser.bundle.js', + format: 'iife', + name: 'AppwriteTest', + sourcemap: false, + inlineDynamicImports: true, + }, + plugins: [ + alias({ + entries: [ + { find: 'react-native', replacement: 'react-native-web' }, + { + find: 'expo-file-system', + replacement: path.resolve(__dirname, 'shims/expo-file-system.js'), + }, + ], + }), + replace({ + preventAssignment: true, + values: { + 'process.env.NODE_ENV': JSON.stringify('production'), + __DEV__: 'false', + }, + }), + resolve({ + browser: true, + extensions: ['.mjs', '.js', '.jsx', '.ts', '.tsx', '.json'], + preferBuiltins: false, + }), + commonjs(), + typescript({ + tsconfig: './tsconfig.json', + noEmitOnError: false, + compilerOptions: { + allowJs: true, + declaration: false, + declarationMap: false, + outDir: 'dist', + rootDir: '.', + }, + include: ['src/**/*.ts', 'browser.js'], + exclude: ['node_modules/**', 'dist/**'], + }), + ], +}; diff --git a/tests/languages/react-native/shims/expo-file-system.js b/tests/languages/react-native/shims/expo-file-system.js new file mode 100644 index 0000000000..412db8d0a2 --- /dev/null +++ b/tests/languages/react-native/shims/expo-file-system.js @@ -0,0 +1,28 @@ +const notImplemented = (name) => () => { + throw new Error( + `expo-file-system shim: ${name} was called, but uploads are not exercised by this test harness.` + ); +}; + +export const EncodingType = { UTF8: 'utf8', Base64: 'base64' }; +export const documentDirectory = ''; +export const cacheDirectory = ''; + +export const getInfoAsync = notImplemented('getInfoAsync'); +export const readAsStringAsync = notImplemented('readAsStringAsync'); +export const writeAsStringAsync = notImplemented('writeAsStringAsync'); +export const deleteAsync = notImplemented('deleteAsync'); +export const uploadAsync = notImplemented('uploadAsync'); +export const FileSystemUploadType = { BINARY_CONTENT: 0, MULTIPART: 1 }; + +export default { + EncodingType, + documentDirectory, + cacheDirectory, + getInfoAsync, + readAsStringAsync, + writeAsStringAsync, + deleteAsync, + uploadAsync, + FileSystemUploadType, +}; diff --git a/tests/languages/react-native/tests.js b/tests/languages/react-native/tests.js new file mode 100644 index 0000000000..dadf56d8f8 --- /dev/null +++ b/tests/languages/react-native/tests.js @@ -0,0 +1,41 @@ +const playwright = require('playwright'); +const handler = require('serve-handler'); +const http = require('http'); +const { exit } = require('process'); + +const server = http.createServer((request, response) => { + return handler(request, response); +}); + +server.listen(3000, async () => { + console.log('Test Started'); + const browser = await playwright[process.env.BROWSER].launch({ + args: [ + '--allow-insecure-localhost', + '--disable-web-security', + ], + }); + const context = await browser.newContext(); + const page = await context.newPage(); + page.on('console', message => { + if (message.type() === 'log') { + console.log(message.text()); + } + }); + page.on('pageerror', err => { + console.log('PAGE ERROR: ' + err.message); + }); + await page.goto('http://localhost:3000'); + + try { + await page.waitForFunction(() => window.__APPWRITE_TEST_DONE__ === true, { + timeout: 20000, + }); + } catch (e) { + console.log('TEST RUNNER TIMEOUT: ' + e.message); + } + + await browser.close(); + server.close(); + exit(0); +});