20210226のNode.jsに関する記事は8件です。

if文なしでじゃんけん - JS版

if文なしでじゃんけん - Qiita を読んでJSで組んでみました。

解答

const gu = "gu";
const choki = "choki";
const pa = "pa";

const createResult = (winHand, loseHand, drawHand) => ({[winHand]: "勝ち", [loseHand]: "負け", [drawHand]: "あいこ"})
const hands = Object.freeze({
    [gu] : {view: "", judgeTable: createResult(choki, pa, gu)},
    [choki] :{view: "", judgeTable: createResult(pa, gu, choki)},
    [pa] : {view: "", judgeTable: createResult(gu, choki, pa)},
});

const getView = (hand) => hands[hand]?.view;
const battle = (my, your) => judge(hands[my]?.judgeTable, your);
const judge = (judgeTable, your) => judgeTable[your];

Object.keys(hands).forEach(myHand => {
    Object.keys(hands).forEach(yourHand => {
        console.log(`[${getView(myHand)} vs ${getView(yourHand)}] ${battle(myHand, yourHand)}`);
    });
});

動作結果
image.png

作ってみて

単純だからこそ考え甲斐があって楽しいですね。
本来であれば自分の手をSymbolで作るべきなのかなと思っていたのですが、出力部分が面倒なことになるので今回は定数という形で実装しました。

三項演算子、Null合体演算子も使うか迷ったのですが使いませんでした。
そのためjudgeで定義している関数で配列かどうかのチェックがありません。でも今回の要件に外からの入力は無いのでヨシとしました。
TypeScriptで実装していればこういった心配が少なくなるのが良いですよね。

今回は文字を出力するだけの要件なので、直接「勝ち」「負け」「あいこ」を書きましたが、関数を持たせるようにすれば勝利ポイントなども扱えるようになりそうです。

もっと短く書けるぞ!って方は是非挑戦してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Scratch3.0】Realtime Database拡張ブロックを作ったので参照実装したら既視感あるアレができちゃった件

事始め

今年、弊社内ではゲーム制作コンテストが開催されており、私は運営チームとしてScratchのサポートを担当しています。Scratchの自主練をしていたところ、楽しそうな記事を見つけました。

そんなことができるのですね!

そしたら、Realtime Database のブロックとかつくって、ネットワークゲームも制作できるようになる......!!!

これはやるしかないでしょ、ということで。

やってみた事とその結果をまとめます。
なおNode.jsは今回初めて触りましたので、変な事していたらそこは温かい目で見守っていただければ幸いです。

環境構築

Windows上で行います。作業ディレクトリは次の場所にしました。

mkdir C:\Users\kami_teru\source\repos\scratch\
cd C:\Users\kami_teru\source\repos\scratch\

以下の記事を参考に、Node.jsをインストールし、scratch-gui をlocalhostで起動できるようにしました。

一つだけ注意点。私のWindows環境では npm run deploy を実行したとき、次のエラーが出ました。

powershell
PS C:\Users\kami_teru\source\repos\scratch\scratch-gui> npm run deploy

> scratch-gui@0.1.0 deploy C:\Users\kami_teru\source\repos\scratch\scratch-gui
> touch build/.nojekyll && gh-pages -t -d build -m "Build for $(git log --pretty=format:%H -n1) [skip ci]"

'touch' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

touchコマンドは、Git for Windowsをインストールしている方なら、次のようにすれば利用可能になります。

powershell
$ENV:Path+=";c:\Program Files\Git\usr\bin"

さてさてこれで、Scratchを試せるようになりました。

  • Scratch 3.0 GUI - 127.0.0.1:8601 Scratch 3.0 GUI - teru.k.business - Microsoft​ Edge 2021_02_26 9_39_51.png

次は、Realtime Database の準備を行います。

Realtime Database のセットアップ

1) Firebase プロジェクトの作成

Firebaseコンソールにアクセスし、プロジェクトを追加します。

設定項目
名前 (任意の名前)
Google アナリティクス 無効にする

アナリティクスは有効にすると、これから作る拡張ブロックの利用頻度などが取得できるようになるのでお勧めですが、本記事に関係のない情報なので無効にします。

2) Firebase プロジェクトに Webアプリ を追加

Scratch 3.0はNode.jsで動作するクライアントサイドのWebアプリなので、次のようにします。

設定項目
アプリのニックネーム (任意の名前)
Firebase Hosting 設定しない

LLK/scratch-gui は、Github Pages にデプロイする仕組みを備えており、本記事もそれを利用します。よって Firebase Hosting は設定しません。

Webアプリを登録すると、 Firebase SDK の追加 のページで「この作業は Firebase サービスを使用する前に行ってください。」という情報が表示されますが、あとで拡張ブロックからやるので、ここではスキップしました。

3) Firebase プロジェクトに Realtime Database を追加

Firebase コンソールの構築メニューから Realtime Database を選択。「データベースを作成」をクリックします。

設定項目
Realtime Databaseのロケーション 米国 (us-central1)
セキュリティルール テストモードで開始

拡張ブロックの動作確認までがとりあえずの目的なので、セキュリティルールはテストモードにしました。

4) Realtime Database への接続情報の確認

「プロジェクトの設定」ページを開くと、全般タブの「マイアプリ」セクションに作成した Webアプリ があります。それを選択すると、 Firebase SDK snippet 欄に次のような接続情報が表示されます。

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/8.2.9/firebase-analytics.js"></script>

<script>
  // Your web app's Firebase configuration
  // For Firebase JS SDK v7.20.0 and later, measurementId is optional
  var firebaseConfig = {
    apiKey: "AI***********************************WQ",
    authDomain: "************.firebaseapp.com",
    databaseURL: "https://************-default-rtdb.firebaseio.com"
    projectId: "************",
    storageBucket: "************.appspot.com",
    messagingSenderId: "************",
    appId: "1:************:web:**********************",
    measurementId: "G-**********",
    };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  firebase.analytics();
</script>

***の部分が、作成したFirebaseプロジェクト固有の部分。

以上で準備完了!では Realtime Database 拡張ブロック づくりへ。

Realtime Database 拡張ブロックの作成

Scratch に Realtime Database 拡張機能 を追加

Scratch で Realtime Database 拡張ブロックを表示できるようにするための機能情報を記述するために、C:\Users\kami_teru\source\repos\scratch\scratch-gui のソースを編集します。

機能名は realtimedbとします。
src/lib/libraries/extensions/index.jsx に以下を追加。

src/lib/libraries/extensions/index.jsx
...(中略)...
import realtimedbIconURL from './realtimedb/realtimedb.png';
import realtimedbInsetIconURL from './realtimedb/realtimedb-small.png';

export default [
...(中略)...
    {
        name: 'Realtime Database',
        extensionId: 'realtimedb',
        iconURL: realtimedbIconURL,
        insetIconURL: realtimedbInsetIconURL,
        description: (
            <FormattedMessage
                defaultMessage="Connect to Realtime Database."
                description="Realtime Database"
                id="gui.extension.realtimedb.description"
            />
        ),
        featured: true,
        internetConnectionRequired: true
    }
]

src/lib/libraries/extensions/realtimedb/ 配下に、画像ファイルを配置。
Firebaseブランドガイドライン から適当なものをピックアップ&サイズを調整。

  • realtimedb.png
    realtimedb.png

  • realtimedb-small.png
    realtimedb-small.png

Realtime Database 拡張機能 の最低限の実装

いよいよ拡張ブロックの実装です。C:\Users\kami_teru\source\repos\scratch\scratch-vm のソースを編集。

まずは SDKの組み込み。Node.js から Realtime Database を利用する場合、Firebase SDK が便利です。
次のようにして、Firebase SDK をインストール。

powershell
cd C:\Users\kami_teru\source\repos\scratch\scratch-vm
npm install firebase --save

次に、Webサイトの Firebase認証 を済ませるための最低限の実装を行います。
こんなブロックを実装していきます。
スクリーンショット 2021-02-26 112416.png

src/extensions/scratch3_realtimedb/index.js
const ArgumentType = require('../../extension-support/argument-type');
const BlockType = require('../../extension-support/block-type');
const Cast = require('../../util/cast');
const log = require('../../util/log');
const firebase = require('firebase/app');
//require('firebase/analytics');
require('firebase/database');

const firebaseConfig = {};

class Scratch3Realtimedb {
    constructor (runtime) {
        this.runtime = runtime;
        this._firebaseApp = undefined;
        this._realtimedb = undefined;
    }
    get firebaseApp () {
        if (this._firebaseApp == undefined)
        {
            this._firebaseApp = firebase.default.initializeApp(firebaseConfig);
            //firebase.default.analytics();
        }
        return this._firebaseApp;
    }
    get realtimeDb () {
        if (this._realtimedb == undefined)
        {
            this._realtimedb = this.firebaseApp.database();
        }
        return this._realtimedb;
    }
    getInfo () {
        return {
            id: 'realtimedb',
            name: 'Realtime Database',
            blocks: [
                {
                    opcode: 'setApiKey',
                    blockType: BlockType.COMMAND,
                    text: '【設定1】apiKey は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setAuthDomain',
                    blockType: BlockType.COMMAND,
                    text: '【設定2】authDomain は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setDatabaseURL',
                    blockType: BlockType.COMMAND,
                    text: '【設定3】databaseURL は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setProjectId',
                    blockType: BlockType.COMMAND,
                    text: '【設定4】projectId は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setStorageBucket',
                    blockType: BlockType.COMMAND,
                    text: '【設定5】storageBucket は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setMessagingSenderId',
                    blockType: BlockType.COMMAND,
                    text: '【設定6】messagingSenderId は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setAppId',
                    blockType: BlockType.COMMAND,
                    text: '【設定7】appId は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setMeasurementId',
                    blockType: BlockType.COMMAND,
                    text: '【設定8】measurementId は [TEXT] を使う',
                    arguments: {
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "Firebase SDK snippetから貼り付けてね"
                        }
                    }
                },
                {
                    opcode: 'setText',
                    blockType: BlockType.COMMAND,
                    text: '[PATH] の [VALUENAME] に [TEXT] をセット',
                    arguments: {
                        PATH: {
                            type: ArgumentType.STRING,
                            defaultValue: "データパス"
                        },
                        VALUENAME: {
                            type: ArgumentType.STRING,
                            defaultValue: "データ項目名"
                        },
                        TEXT: {
                            type: ArgumentType.STRING,
                            defaultValue: "データ値"
                        }
                    }
                }
            ],
            menus: {
            }
        };
    }
    setApiKey(args)
    {
        firebaseConfig.apiKey = Cast.toString(args.TEXT);
    }
    setAuthDomain(args)
    {
        firebaseConfig.authDomain = Cast.toString(args.TEXT);
    }
    setDatabaseURL(args)
    {
        firebaseConfig.databaseURL = Cast.toString(args.TEXT);
    }
    setProjectId(args)
    {
        firebaseConfig.projectId = Cast.toString(args.TEXT);
    }
    setStorageBucket(args)
    {
        firebaseConfig.storageBucket = Cast.toString(args.TEXT);
    }
    setMessagingSenderId(args)
    {
        firebaseConfig.messagingSenderId = Cast.toString(args.TEXT);
    }
    setAppId(args)
    {
        firebaseConfig.appId = Cast.toString(args.TEXT);
    }
    setMeasurementId(args)
    {
        firebaseConfig.measurementId = Cast.toString(args.TEXT);
    }
    setText(args) {
        const path = Scratch3Realtimedb.parsePathInput(args.PATH);
        const valueName = Cast.toString(args.VALUENAME);
        const text = Cast.toString(args.TEXT);
        var data = {};
        data[valueName] = text;
        this.realtimeDb.ref(path).set(data);
    }
}
module.exports = Scratch3Realtimedb;

※アナリティクスを利用する場合はコメントアウトの箇所を有効にします。

最低限の実装ができたら、それを機能の一覧に登録するために記述を追加。

src/extension-support/extension-manager.js
const builtinExtensions = {
...(中略)...
    realtimedb: () => require('../extensions/scratch3_realtimedb')
};

では、ローカルで動作確認してみます。ビルドしてスタート。

powershell
cd C:\Users\kami_teru\source\repos\scratch\scratch-vm
yarn install; yarn link
cd C:\Users\kami_teru\source\repos\scratch\scratch-gui
yarn link scratch-vm; yarn install
yarn start
  • Realtime Database 拡張機能が表示されていますね!
    Scratch 3.0 GUI - teru.k.business - Microsoft​ Edge 2021_02_26 11_38_45.png

  • Firebase認証を通すためのプログラムを作って、一度実行。※アナリティクスを利用する場合は、設定8も実行します。
    スクリーンショット 2021-02-26 115154.png
    ※上記の認証情報は記事公開時点で削除しているので利用できません。

一度目はFirebase認証を通すだけになるので、Realtime Databaseへの書き込みは失敗するようです。
でもこれでWebサイトとしての認証が通りました。

  • 以降は次の設定だけのプログラムで、RealtimeDatabaseに接続できます。
    スクリーンショット 2021-02-26 115509.png

  • ちゃんと書き込めていますね!
    scratch-link - Firebase コンソール - teru.k.business - Microsoft​ Edge 2021_02_26 11_57_11.png

これと同じ手順を、Github Pagesでも行います。デプロイ。

powershell
yarn run build
yarn run deploy

同じように、Github Pagesに公開した Scratch からも Realtime Database に書き込めることが確認できました。

Realtime Database 拡張機能 の本実装

以下の技術情報を参考に、さらに機能を付け加えていきます。

以下、実装コードは、github のリンクから併読ください。

機能 ブロックイメージ ブロック情報 実装
データ階層の表現 スクリーンショット 2021-02-26 120453.png getInfo concatPath
文字データの送信 スクリーンショット 2021-02-26 122506.png getInfo setText
データ変更の監視 スクリーンショット 2021-02-26 122706.png getInfo listen
データ追加の監視 スクリーンショット 2021-02-26 122734.png getInfo listenChildAdd
データ受信の判定 スクリーンショット 2021-02-26 122758.png getInfo whenReceived
文字データの取得 スクリーンショット 2021-02-26 122831.png getInfo receiveText
追加された子階層名の取得 スクリーンショット 2021-02-26 122859.png getInfo receiveChildKey

ではいよいよ、これを使い方を示すものとなる参照実装を行います。

Realtime Database 拡張ブロック の参照実装

あいさつをほかのユーザーと共有してみた

まずは指定したパスの「データ変更の監視」と「データ受信の判定」と「文字データの取得」を中心に確認。
こんなプログラムにしてみました。
スクリーンショット 2021-02-26 161941.png

動かしてみましょう。
動作を確認するために、簡単なページを作りました。
※Github-Pagesのscratch-guiをiframeで呼び出してちょっと小細工したものです。
test01-1-min.gif

(2)で表示されたあいさつが、(1)のほうにも表示されましたね?

データはこんな感じになりました。
スクリーンショット 2021-02-26 163310.png

どんどんいきましょう。

ユーザーが部屋に入ったらキャラクターを表示するようにしてみた

つぎは指定したパスへの「データ追加の監視」と「追加された子階層名の取得」を中心に確認。
プログラムはこんな感じ。

ねこ おともだち
スクリーンショット 2021-02-26 164314.png スクリーンショット 2021-02-26 164328.png

動かしてみましょう。

test01-2-min.gif

おぉぉ!お友達が部屋を訪れましたね!!?
じろうさんの部屋にも、たろうさんがちゃんといます。

データはこんな感じになりました。
スクリーンショット 2021-02-26 174005.png

どんどん動かしてみた/つぶやけるようにしてみた

今度は Realtime Database拡張ブロックは使っていませんが・・・つぶやく機能を追加。

ねこ(追加分) おともだち
スクリーンショット 2021-02-26 165033.png 変更なし

まずはどんどん増やして・・・以下、9人目と10人目から。
test01-3-min.gif

おぉぉぉ、なかなかそうだいな画になってきましたね?
さらに20人追加してみましょう。性能テストですね。

スクリーンショット 2021-02-26 171350.png

ともだちがいっぱいでも大丈夫!???

これで好きな部屋に入って、会話を楽しみむことが出来ますね!!

でも・・・

あれっ?

こんなアプリ、どこかで見たような・・・

??? - 完 - ???

おわりに

例のパロディ作品のパクリでした(汗

なお今回作ったものは、以下の場所で公開しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Node.js] MySQLのIN句をプレースホルダーで表す

概要

Node.jsでMySQLを扱うにあたり、
IN句のリスト部分をプレースホルダーで表すには、どのようにすれば良いかを以下に記していきます。

本題の前に...

(本題しか興味ない人は、すっ飛ばしてください)

Node.jsでMySQLを扱うためにインストール

npm i -S mysql promise-mysql

Node.jsでMySQLプレースホルダーを扱うときの基本

例えば、プレースホルダーなしで書こうとすると以下のようになります。(良くない例です)

【NG例】プレースホルダーなし
const userid = 1 ;
const sql = `SELECT * FROM users WHERE userid = ${userid} ;`

このままだと、SQLインジェクションの格好の餌食となってしまうため、
プレースホルダーを使って書き直します。

【OK例】プレースホルダーあり
const userid = 1;
const sql = `SELECT * FROM users WHERE userid = ? ;`
const res = await conn.query(sql, userid);
//この場合、
//SELECT * FROM users WHERE userid = 1;
//というクエリを投げたことになる

変数が2つ以上になる場合は、

【OK例】変数が2つ以上の場合
const userid = 1;
const classname = 'G';
const sql = `SELECT * FROM users WHERE userid = ? AND classname = ? ;`
const res = await conn.query(sql, [userid, classname] );
//この場合、
//SELECT * FROM users WHERE userid = 1 AND classname = 'G';
//というクエリを投げたことになる

のように、配列に変数を格納することで表すことができます。

本題「IN句のリストをプレースホルダで表したい」

以下のように書くことで、IN句もプレースホルダーを用いて表すことができます。

IN句でプレースホルダーを使う
const sql = `SELECT * FROM users WHERE userid NOT IN (?) ;`
const excludedId = [1, 3, 4];
const res = await conn.query(sql, [excludedId] );

これで、userid = 1, 3, 4 以外の行を抽出することができました。

ちょっとだけ解説

複数の変数がある場合、その変数の個数分「?」を用意する必要があると上述しました。
今回の場合、

リストを?で表す
const excludedId = [1, 3, 4];
const sql = `SELECT * FROM users WHERE userid NOT IN (?,?,?) ;`

のように書かれていてほしいわけです。
しかしながら、状況によって
?の個数が3つから4つ、5つと変動していくことも考えられ、上記のように書くのは現実的ではありません。

そこで、
「1,3,4をそれぞれ変数として見る」のではなく、
「[1,3,4]という配列を、1つの変数として捉える」ことで解決できます。
リストに渡したい配列さえあれば、その配列を引数に渡すだけで
簡単にクエリを作ることが出来ました。

注意点

ちなみに、以下のやり方ではエラーが出ます。

【NG例】IN句でプレースホルダーを使う
const sql = `SELECT * FROM users WHERE userid NOT IN (?) ;`
const excludedId = [1, 3, 4];
const res = await conn.query(sql, excludedId);

複数の変数が出てくる場合は
[userid, classname]のように配列に変数を格納して、渡しますよね。

しかし今回の場合、[1,3,4]という配列を一つの変数として扱いたいので、
const res = await conn.query(sql, excludedId );
のように配列を渡してしまうと、
その中の要素である、1,3,4という3つの数値が、変数として見られてしまいます。

そのため必ず、配列を配列で括って書くようにしましょう。

余談「Node.jsでクエリを確認するためには」

プレースホルダーで書いていると、
実際、自分の思ったところに正しい変数が入っているか不安になることもあると思います。
そこで、以下のような書き方をすると
実際に投げられているクエリを確認することができ、大変便利です。

クエリ確認
const excludedId = [1, 3, 4];
const sql = mysql.format(`select * from users where userid NOT IN (?) ;`, [excludeId] );
console.log(sql); //select * from users where userid NOT IN (1,3,4) ;

終わりに

IN句のリストをプレースホルダーで表している例があまりなかったので
先輩の助けを借り、今回記事にしてみました。
参考になれば幸いです。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHub コマンドでcloneからpushまでの手順

超初心者向け

~gitコマンドでcloneからcommitまでの手順~

自分のFinderのgitをcloneしたい場所に移動する

cd "ローカルリポジトリーを置きたい場所"

移動先にgitのリモートリポジトリーを設置する

git clone "GitHub CLIのURL"

自分のGitHub CLIのURLはWEB版のgithubの自分のリポジトリーにいき
緑色のcodeを押すと出てきます。

そして自分のbranchを確認します。

git branch

すると、*mainがでてくると思います。
そこで新しくbranchを作成します。

git branch 新しいbranch名

これでbranchは作成できます。その後branchの切り替えを行います

一人で制作する場合は直接mainを使っていいかなとなるかもしれませんが
そう思う人はTECH ACADEMYさんのこの記事を見てもらうのがわかりやすいと思います。

git checkout 作成したbranch名

この2つを一緒に行えるコマンドもあるので、追記しておきます。
git checkout -b 新しいbranch名

Gitのリモートリポジトリー上にファイルを使加する
git add *

*でフォルダ上のフォルダ、ファイルがすべて追加されます。

ローカルリポジトリーからリモートリポジトリーにコミットする(-mの先はコミットするときのコメント内容)
git commit -m "add file goodmorning"

自分のブランチにmainにpullリクエストを送る
git push origin 作成したbranch名

ここまでがcloneからpushまでの流れです。

ここからはnpm iした際のリモートリポジトリーに上げる際にnode_modulesのあげない設定です
node_modulesに関してはpakage.json,pakage.json-look.jsonがあればnpm iでダウンロードできる
ためリモートリポジトリーに上げる必要もないですし、ファイル数が多いためおすすめしません

フォルダ直下に
touch .gitignore

作成します。そして.gitignoreの中に記述する内容は
node_modules/
ファイルのパターン一致でコンパイルなどで生成したファイルを指定する場合
*.com
*.class
*.dll
*.exe
*.o
*.so

これでnode_modulesだけリモートリポジトリーに上がらないようになります

あくまで僕のメモとして作成致しましたが誰かの役にたてると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【テスト自動化】Playwrightでファイルのアップロードのテスト

Playwrightでファイルのアップロードのテスト【テスト自動化】

Playwrightでテスト自動化をかんたんに
・インストール等は他ドキュメントが詳しいので省略します
・ファイルのアップロードが必要な場合

環境

・Windows10 64bit
・node.js v14.15.5
・cygwin

Playwright とは

Playwrightは、Chromium、Firefox、WebKitを単一のAPIで自動化するNode.jsライブラリです。
常に環境に配慮し、機能があり、信頼性が高く、高速なクロスブラウザーWeb自動化を可能にするように構築されています。

個人的な感想ですが、「Windows で webkit (safari) も実行できる」ブラウザテスト自動化用のツール。
Selenium(最近はあまり触っていないですが・・・)より使った感じは良好。
待ちの処理も優秀でかなり使いやすいと感じました。あと動画が撮れちゃうのもイイですね。
いろいろな言語に対応してますが、ここでは node.js のスクリプトで載せます。

ブラウザ起動など

npx playwright wk https://www.wikipedia.org/

image.png

・使い方
image.png
codegen とするとブラウザが起動し操作した内容をスクリプトにしてくれます。(最初はこれを使うと便利)

ファイルをアップロードさせる方法

    const [ fileChooser ] = await Promise.all([
        page.waitForEvent('filechooser'),
        page.click('text="ファイルを選択"'),
    ]);

    await fileChooser.setFiles('c:/path/to/upload.jpg');
    await page.waitForSelector('text="upload.jpg"');

text="ファイルを選択" の所がファイル選択ダイアログを開くボタン。
ダイアログを開く際に、filechooser と一緒に待たせることでファイルをアップロードすることができます。
最後の waitForSelector ではアップロード後のファイル名を(jsで)表示させ、検知させてアップロード完了まで待たせてます。
この辺は実装内容によって変わってくると思います。

span にある内容を取得したい場合(おまけ)

const spanText = await page.$eval('//span[contains(text(),"%s")]/following-sibling::span[1]'.replace("%s", target),
                    el => el.innerHTML);

※target 文字列の入ったspan の次の span の中身を取得
xpath で自由に設定できるので自在に取れます。

video を撮影したい場合(おまけ)

    const context = await browser.newContext({
        locale: 'ja-JP',
        timezoneId: 'Asia/Tokyo',
        viewport: { width: 1000, height: 900 },
        recordVideo: { dir: 'd:/tmp/playwright/videos/' },
    });

コンテキストの設定に設定すると勝手に撮影して保存してくれます。

以上、お疲れさまでした!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】JavaScriptの非同期処理について

プログラミング勉強日記

2021年2月26日

同期処理とは

 同期処理は、コードを順番に処理していって、1つの処理が終わるまで次の処理は行われない。同期処理では、実行している処理は1つだけとなるので直感的な動作になる。
 しかし、1つの処理が終わるまで次の処理ができないことによっての問題点も存在する。具体的には、同期的にブロックする処理があると大きな問題が生じる。JavaScriptでは基本的にブラウザのメインスレッドで実行されるので、メインスレッドが他の処理でいっぱいになってしまうと表示が更新されなくなってしまいフリーズしたような状況になる。これは、メインスレッドが表示の更新といった処理を行っているためである。

非同期処理とは

 非同期処理でもコードを順番に処理していくが、1つの非同期処理が終わるを待たずに次の処理を行うことができる。なので、非同期処理では同時に実行している処理が複数ある。
 JavaScriptにおける非同期処理には、コールバック、Promise、async/awaitの3種類ある。JavaScriptにおいての多くの非同期処理はメインスレッドで実行される。

 JavaScriptでは一部の例外を除いて、非同期処理が並行処理として扱われる。並行処置は、処理をある一定の単位ごとに分けて処理を切り替えながら実行することである。なので、非同期処理であってもその処理の実行中に重い処理があると非同期処理の切り替えが遅れる可能性もある。
 そのため、非同期処理の中でもメインスレッドとは別のスレッドで実行できるAPIが存在する。これによって、排他的に複数の処理を同時に実行することのできる並列処理をすることができる。
 そうはいっても、基本的な非同期処理はメインスレッドで実行されているという性質を知ることは大切である。

参考文献

JavaScriptの非同期処理を理解する その1 〜コールバック編〜
非同期処理:コールバック/Promise/Async

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【imi-enrichment-date】年号を含む日付表示の正規化

imi-enrichment-dateとは

経産省のジービスインフォというサイトで公開されているオープンソースライブラリの1つで、
令和3年2月26日のような日付表示を2021-02-26といった形式に正規化することができます。
MITライセンスで公開されているので商用利用も可能です。

インストールの仕方

このツールはnpmのようなパッケージ頒布サイトでは公開されていないようで、
経産省のサイトあるいはプロジェクトのGitHubページからダウンロードして用います。

# on your own project
npm install https://info.gbiz.go.jp/tools/imi_tools/resource/imi-enrichment-date/imi-enrichment-date-1.0.0.tgz

使い方

ここでは自分のプロジェクトに組み込んで使う方法を紹介します。

ライブラリから適当な名前で読み込んだ関数は、引数に2021年2月26日令和三年二月二六日のような日付を表す文字列か、{"@type": "日付型", "表記": "2021年2月26日"}のようなオブジェクトを引数にとります。

const normarizeDate = require("imi-enrichment-date");
console.log(normarizeDate("令和3年2月26日"));
// or
const date = {
    "@type": "日付型",
    "表記": "令和3年2月26日"
};
console.log(normarizeDate(date));
出力
{
  '@context': 'https://imi.go.jp/ns/core/context.jsonld',
  '@type': '日付型',
  '表記': '令和3年2月26日',
  '標準型日付': '2021-02-26'
}

出力はyyyy-mm-dd形式で、上記のようなオブジェクトとして返ってきます。

どのように変換を行っているのか気になったのでソースコードを見てみると、「漢数字・全角の算用数字を半角の算用数字に変換」→「年号が含まれる場合は正規表現で処理」といった流れで変換がなされているようでした。

どのようなパターンに対応しているのか

日付表示にはさまざまなフォーマットがありますが、このライブラリが対応しているフォーマットは以下のとおりです。

形式 備考
年・月・日がなんらかの文字で区切られているもの 2020/01/01 区切り文字は数字以外ならなんでもよい
先頭に元号を含むもの 平成10年10月10日 明・昭など一文字や、㍽・㋿のような合字、あるいはH・hのようなアルファベットでもよい
漢数字を含むもの 一九九九年十二月三〇日 三十ではなく三〇のような書き方をしてもOK
元年という表現を含むもの 令和元年3月24日

2/26/2021(2021年2月26日)のようなアメリカ式の日付表示には対応していませんが、日本で使われている日付表示の方式にはほぼ対応しているライブラリといえます。

ひとつだけ気をつけるべきこととしては、21年2月26日のように年の上二桁を省略した書き方をすると、意図せず1921-2-26となってしまう点があります。これはエラーにはならないので注意が必要です。

また(これはあまり一般的ではありませんが)、西暦2021年2月26日のような表現はエラーになります。役所に行ってなにか申請書を書こうとすると、最近では配慮が進んでいて年号ではなく「西暦」に丸がつけられるようになっていることがありますよね。西暦と年号表示を並行して使用しているシステムでは注意が必要です。

リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RustでWebAssemblyを使ってみた時の記録

この記事について

プログラミング独学3年目の文系大学生の開発メモ。

なぜWebAssemblyなのか

特に理由はありません(笑)
最近話題になっていると聞いて触ってみました。

なぜRustなのか

これも特に理由なしです。
システムプログラミング言語なのに低レベルだけではなく、色々なアプリケーションを開発するためのライブラリが揃っていると聞いて興味を持ちました。

開発環境 (2021/02/26現在)

Windows10 Education バージョン 20H2 (Windows10 Home バージョン 20H2上のVMware Workstation Player 16仮想マシン)
Visual Studio Code
gcc 8.1.0
node 11.13.0
npm 6.9.0
rustc 1.50.0
cargo 1.50.0

開発開始

MDNに掲載されているRust用のWebAssembly入門に沿ってやります。
WebAssembly自体をもっと知りたいという方はこちらから確認してください。

開発メモ

  • Rustをインストール際にC++のBuild Toolsのインストールを促された。 これを無視してやったらcargo install wasm-packするときにエラーが出た。 普段はVisual Studio Communityを使うことがほぼ無かったので、インストーラーを入れ、C++のデスクトップ開発用のツールを一通り入れたらエラーは治った。 (今回とは関係ないが、gccを入れておかないとエラーが出る場合もあるらしい。)
  • Rustのコード内の#[wasm_bindgen]という記述はアトリビュートと呼ばれ、直下の文のみを修飾してくれるらしい。 あくまで直下のみなので、何度も記述されてるからと言って2個目以降を省いてはダメ。
  • Rustにおいて「クレート」とは他言語でいうライブラリやパッケージのこと。Rustの特徴として、クレートを芋づる式で追って解析していくため、初回ビルド時のみ時間がかかる。
  • Rustにはクラスは存在しないけどstruct(構造体)にはimpl(インプリメンテーション)という特殊な構文が存在し、構造体にメソッドをつけることが可能。 だから、実質オブジェクト指向に開発できる。
  • npmでpublicに公開するとき、package.jsonのnameに@xxxを含む記述があった場合、組織を予め用意しておく必要がある。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む