Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration test to load the default example of the default SDK and change the example #24731

Merged
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ website/www/yarn-error.log
**/.packages
**/generated_plugin_registrant.dart
playground/frontend/playground_components/pubspec.lock
playground/frontend/playground_components_dev/pubspec.lock

# Ignore Beam Playground Terraform
**/.terraform
Expand Down
2 changes: 1 addition & 1 deletion learning/tour-of-beam/frontend/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ environment:
flutter: '>=3.3.2'

dependencies:
app_state: ^0.8.1
app_state: ^0.8.4
collection: ^1.16.0
easy_localization: ^3.0.1
easy_localization_ext: ^0.1.0
Expand Down
31 changes: 31 additions & 0 deletions playground/frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,37 @@ Code can be automatically reformatted using:
flutter format ./lib
```

### Unit Tests

To delete all generated files and re-generate them again and then run tests:

```bash
./gradlew :playground:frontend:playground_components_test
./gradlew :playground:frontend:test
```

To run tests without re-generating files:

```bash
cd playground/frontend/playground_components
flutter test
cd ..
flutter test
```

### Integration Tests

Integration tests currently can be run only on a local development machine.
Server testing has not been verified yet.

1. Install and run Chrome Driver: https://chromedriver.chromium.org/downloads
2. Run it on port 4444: `chromedriver --port=4444`
3. Run:

```bash
./gradlew :playground:frontend:integrationTest
```

## Localization

The project is in the process of migrating from
Expand Down
131 changes: 52 additions & 79 deletions playground/frontend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,11 @@
*/


apply plugin: 'org.apache.beam.module'
apply plugin: 'base'
apply(plugin: "org.apache.beam.module")
apply(plugin: "base")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preparing the future migration to the KTS format.

applyDockerNature()

def playgroundBackendUrl = project.playgroundBackendUrl
def analyticsUA = project.analyticsUA
def playgroundBackendJavaRouteUrl = project.playgroundBackendJavaRouteUrl
def playgroundBackendGoRouteUrl = project.playgroundBackendGoRouteUrl
def playgroundBackendPythonRouteUrl = project.playgroundBackendPythonRouteUrl
def playgroundBackendScioRouteUrl = project.playgroundBackendScioRouteUrl

def playgroundJobServerProject = "${project.path.replace('-container', '')}"
def playgroundJobServerProject = "${project.path.replace("-container", "")}"

description = project(playgroundJobServerProject).description + " :: Container"

Expand All @@ -37,10 +30,10 @@ configurations {
}

dependencies {
dockerDependency project(path: playgroundJobServerProject, configuration: "shadow")
dockerDependency(project(path: playgroundJobServerProject, configuration: "shadow"))
}

task generate {
tasks.register("generate") {
dependsOn("playground_components:generate")

dependsOn("generateCode")
Expand All @@ -49,7 +42,7 @@ task generate {
description = "Generates all generated files."
}

task printPath {
tasks.register("printPath") {
doLast {
exec {
executable("printenv")
Expand All @@ -58,7 +51,7 @@ task printPath {
}
}

task analyze {
tasks.register("analyze") {
dependsOn("playground_components:generateCode")
dependsOn("generateCode")

Expand All @@ -74,7 +67,7 @@ task analyze {
}
}

task pubGet {
tasks.register("pubGet") {
group = "build"
description = "Get packages for the playground frontend project"
doLast {
Expand All @@ -85,7 +78,7 @@ task pubGet {
}
}

task format {
tasks.register("format") {
group = "build"
description = "Idiomatically format Dart source code"
doLast {
Expand All @@ -97,9 +90,10 @@ task format {
}
}

task run {
tasks.register("run") {
group = "application"
description = "Run application on Google Chrome"

doLast {
exec {
executable("flutter")
Expand All @@ -108,7 +102,7 @@ task run {
}
}

task test {
tasks.register("test") {
dependsOn("playground_components:generateCode")
dependsOn("generateCode")

Expand All @@ -123,14 +117,14 @@ task test {
}
}

task precommit {
tasks.register("precommit") {
dependsOn("playground_components:precommit")

dependsOn("analyze")
dependsOn("test")
}

task generateCode {
tasks.register("generateCode") {
dependsOn("playground_components:generateCode")

dependsOn("cleanFlutter")
Expand All @@ -147,7 +141,7 @@ task generateCode {
}
}

task cleanFlutter {
tasks.register("cleanFlutter") {
group = "build"
description = "Remove build artifacts"

Expand All @@ -159,7 +153,7 @@ task cleanFlutter {
}
}

task cleanGenerated {
tasks.register("cleanGenerated") {
dependsOn("playground_components:cleanGenerated")

group = "build"
Expand Down Expand Up @@ -188,75 +182,54 @@ ext.deleteFilesByRegExp = { re ->
}
}

tasks.register("integrationTest") {
dependsOn("integrationTest_standalone_change_example")
}

tasks.register("integrationTest_standalone_change_example") {
runIntegrationTest("standalone_change_example", "/")
}

void runIntegrationTest(String path, String url) {
exec {
executable("flutter")
args(
"drive",
"--driver=test_driver/integration_test.dart",
"--target=integration_test/${path}_test.dart",
"--web-launch-url='$url'",
"--device-id=chrome",
)
}
}

task copyDockerfileDependencies(type: Copy) {
group = "build"
description = "Copy files that required to build docker container"
copy {
from '.'
into 'build/'
exclude 'build'
exclude 'Dockerfile'
from(".")
into("build/")
exclude("build")
exclude("Dockerfile")
}
copy {
from '../playground'
into 'build/playground'
from("../playground")
into("build/playground")
}
}

docker {
group = "build"
description = "Build container for playground frontend application"
name containerImageName(
name: project.docker_image_default_repo_prefix + "playground-frontend",
root: project.rootProject.hasProperty(["docker-repository-root"]) ?
project.rootProject["docker-repository-root"] :
project.docker_image_default_repo_root)
files "./build/"
tags containerImageTags()
buildArgs(['FLUTTER_VERSION': project.rootProject.hasProperty(["flutter-version"]) ?
project.rootProject["flutter-version"] :
"3.3.2" ])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name = containerImageName(
name: project.docker_image_default_repo_prefix + "playground-frontend",
root: project.rootProject.hasProperty(["docker-repository-root"])
? project.rootProject["docker-repository-root"]
: project.docker_image_default_repo_root
)
files("./build/")
tags(containerImageTags())
}

// Ensure that we build the required resources and copy and file dependencies from related projects
dockerPrepare.dependsOn copyDockerfileDependencies

task("createConfig") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer used for config.g.dart generation.

group = "build"
description = "Generate config for the playground frontend project"
doLast {
def configFileName = "config.g.dart"
def modulePath = project(":playground:frontend").projectDir.absolutePath
def file = new File(modulePath + "/lib", configFileName)
file.write("""/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const String kApiClientURL =
'${playgroundBackendUrl}';
const String kAnalyticsUA = '${analyticsUA}';
const String kApiJavaClientURL =
'${playgroundBackendJavaRouteUrl}';
const String kApiGoClientURL =
'${playgroundBackendGoRouteUrl}';
const String kApiPythonClientURL =
'${playgroundBackendPythonRouteUrl}';
const String kApiScioClientURL =
'${playgroundBackendScioRouteUrl}';
""")
}
}
dockerPrepare.dependsOn(copyDockerfileDependencies)
37 changes: 37 additions & 0 deletions playground/frontend/integration_test/common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:playground/main.dart' as app;
import 'package:playground/modules/examples/example_selector.dart';

Future<void> init(WidgetTester wt) async {
app.main();
await wt.pumpAndSettle();
}

extension CommonFindersExtension on CommonFinders {
Finder exampleSelector() {
return byType(ExampleSelector);
}

Finder exampleItemInDropdown(String name) {
return widgetWithText(GestureDetector, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:playground_components_dev/playground_components_dev.dart';

import 'common.dart';

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('Integration.', () {
testWidgets('Change example', (WidgetTester wt) async {
await init(wt);

expect(
wt.findOneCodeController().lastTextSpan!.toPlainText(),
await Examples.getJavaVisibleText(ExamplePaths.javaMinimalWordCount),
);

await wt.tap(find.exampleSelector());
await wt.pumpAndSettle();

await wt.tap(find.exampleItemInDropdown(ExampleNames.aggregationMax));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think exampleSelector and exampleItemInDropdown methods are necessary here? It's one-liner methods, which increase nesting code here. Looks like they make code more verbose

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For exampleItemInDropdown:
It hides the lookup of GestureDetector which is an implementation detail. If we ever change to another target in this lookup, we will have hard time replacing GestureDetector.

For both:

  1. When you type find., you can see the suggestions instead of picking the widget class.
  2. We can find all tests using this method. That is a more narrow result than looking for all uses of ExampleSelector.
  3. Lines get shorter which is important with the Dart column limit.
  4. More consistent if we must have exampleItemInDropdown anyway.

await wt.pumpAndSettle();

expect(
wt.findOneCodeController().lastTextSpan!.toPlainText(),
await Examples.getJavaVisibleText(ExamplePaths.javaAggregationMax),
);
});
});
}
2 changes: 1 addition & 1 deletion playground/frontend/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void main() async {

// Router API specific initialization.
final pageStack = GetIt.instance.get<PageStack>();
final routerDelegate = PageStackRouterDelegate(pageStack);
final routerDelegate = BeamRouterDelegate(pageStack);
final routeInformationParser = PlaygroundRouteInformationParser();
final backButtonDispatcher = PageStackBackButtonDispatcher(pageStack);

Expand Down
Loading